/*
 * QuickJS Javascript Engine
 *
 * Copyright (c) 2017-2019 Fabrice Bellard
 * Copyright (c) 2017-2019 Charlie Gordon
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
#include <assert.h>
#include <fenv.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#include "gc/trace-gc.h"

#if defined(_WIN32)
#if defined(_MSC_VER)
#include <BaseTsd.h>
typedef SSIZE_T ssize_t;
#endif
#include <windows.h>
#include <winsock.h>
extern "C" {
#include "quickjs/include/quickjs-libc.h"
}
#include <memoryapi.h>
#else
#include <sys/time.h>
#endif

#ifdef ENABLE_TRACING_GC_LOG
#include <time.h>
#endif

#include <errno.h>
#ifndef _WIN32
#include <pthread.h>
#include <unistd.h>
#endif

#include <cstdint>
#include <cstdlib>
#include <sstream>

#if defined(__APPLE__)
#include <malloc/malloc.h>
#elif defined(__linux__)
#include <malloc.h>
#endif

#ifdef __cplusplus
extern "C" {
#endif
#include "quickjs/include/libregexp.h"
#include "quickjs/include/quickjs_version.h"
#ifdef __cplusplus
}
#endif

#include "gc/collector.h"
#include "gc/global-handles.h"
#include "gc/sweeper.h"
#include "gc/thread_pool.h"
#include "quickjs/include/bignum.h"
#include "quickjs/include/quickjs-inner.h"

#if defined(ENABLE_TRACING_GC_LOG) || defined(ENABLE_GC_DEBUG_TOOLS) || \
    defined(ENABLE_FORCE_GC)
#include <iostream>
#endif

#if !defined(BAZEL_TEST) && !defined(__WASI_SDK__) && \
    defined(ENABLE_QUICKJS_DEBUGGER)
#include "inspector/interface.h"
#endif  // BAZEL_TEST

#if defined(ANDROID) || defined(__ANDROID__)
#include <android/log.h>
#endif

#ifndef EMSCRIPTEN
#define EMSCRIPTEN
#endif

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
#pragma clang diagnostic ignored "-Wunused-variable"
#pragma clang diagnostic ignored "-Wconditional-uninitialized"
#pragma clang diagnostic ignored "-Wunreachable-code"
#pragma clang diagnostic ignored "-Wunused-function"
// <Primjs end>
#if defined(EMSCRIPTEN)
#define DIRECT_DISPATCH 0
#else
#define DIRECT_DISPATCH 1
#endif

#if defined(__APPLE__)
#define MALLOC_OVERHEAD 0
#else
#define MALLOC_OVERHEAD 8
#endif

#if !defined(_WIN32) && !defined(__WASI_SDK__)
/* define it if printf uses the RNDN rounding mode instead of RNDNA */
#define CONFIG_PRINTF_RNDN
#endif

/* define to include Atomics.* operations which depend on the OS
   threads */
#if !defined(EMSCRIPTEN)
#define CONFIG_ATOMICS
#endif

/* dump object free */
// #define DUMP_FREE
// #define DUMP_CLOSURE

/* dump the occurence of the automatic GC */
// #define DUMP_GC
/* dump objects freed by the garbage collector */
// #define DUMP_GC_FREE

/* dump memory usage before running the garbage collector */
// #define DUMP_MEM 1
// #define DUMP_OBJECTS    /* dump objects in JS_FreeContext_GC */
// #define DUMP_ATOMS      /* dump atoms in JS_FreeContext_GC */
// #define DUMP_SHAPES     /* dump shapes in JS_FreeContext_GC */
// #define DUMP_MODULE_RESOLVE
// #define DUMP_PROMISE
// #define DUMP_READ_OBJECT

#if (defined(DUMP_LEAKS) || defined(DUMP_BYTECODE) || defined(DUMP_OBJECTS) || \
     defined(DUMP_ATOMS) || defined(DUMP_SHAPES) || defined(DUMP_GC_FREE) ||   \
     defined(DUMP_FREE)) &&                                                    \
    !defined(DUMP_QJS_VALUE)
#define DUMP_QJS_VALUE
#if defined(QJS_UNITTEST) && defined(printf)
#undef printf
#endif
#endif

/* test the GC by forcing it before each object allocation */
// #define FORCE_GC_AT_MALLOC

#ifdef CONFIG_ATOMICS
#include <stdatomic.h>
#endif

// <primjs begin>
#define UNLIKELY(condition) (__builtin_expect(!!(condition), 0))
#define LIKELY(condition) (__builtin_expect(!!(condition), 1))

#if defined(ENABLE_PRIMJS_SNAPSHOT)
static const int NUM_OF_TOS_STATES = 3;
#endif

#if defined(ENABLE_PRIMJS_SNAPSHOT)
static pthread_mutex_t prim_init_mutex = PTHREAD_MUTEX_INITIALIZER;

static bool IS_PRIM_INITIALIZED = false;

#endif

#if defined(ENABLE_PRIMJS_TRACE) && PRINT_LOG_TO_FILE && \
    (defined(ANDROID) || defined(__ANDROID__))
FILE *log_f = nullptr;
#endif

// <primjs end>

/* number of typed array types */
#define JS_TYPED_ARRAY_COUNT \
  (JS_CLASS_BIG_UINT64_ARRAY - JS_CLASS_UINT8C_ARRAY + 1)
/* Typed Arrays */
static uint8_t const typed_array_size_log2[JS_TYPED_ARRAY_COUNT] = {
    0, 0, 0, 1, 1, 2, 2, 2, 3, 3, 3};

#define typed_array_size_log2(classid) \
  (typed_array_size_log2[(classid)-JS_CLASS_UINT8C_ARRAY])

#define JS_MAX_LOCAL_VARS 65535
#define JS_STACK_SIZE_MAX 65535
#define JS_STRING_LEN_MAX ((1 << 30) - 1)

#ifdef ENABLE_GC_DEBUG_TOOLS
void add_cur_node(LEPUSRuntime *rt, void *node, int type) {
  if (!node) return;
  if (type == 1) {  // handle
    rt->gc->handle_order_cnt++;
    rt->gc->cur_handles[node] = rt->gc->handle_order_cnt;
  } else if (type == 2) {  // qjsvaluevalue
    rt->gc->qjsvalue_order_cnt++;
    rt->gc->cur_qjsvalues[node] = rt->gc->qjsvalue_order_cnt;
  }
}

void delete_cur_node(LEPUSRuntime *rt, void *node, int type) {
  if (!node) return;
  if (type == 1) {  // handle
    rt->gc->cur_handles.erase(node);
  } else if (type == 2) {  // qjsvaluevalue
    rt->gc->cur_qjsvalues.erase(node);
  }
}

size_t get_cur_node_cnt(LEPUSRuntime *rt, void *node, int type) {
  if (!node) return 0;
  std::unordered_map<void *, size_t>::iterator it;
  if (type == 1) {
    it = rt->gc->cur_handles.find(node);
    if (it != rt->gc->cur_handles.end()) {
      return it->second;
    }
  } else if (type == 2) {
    it = rt->gc->cur_qjsvalues.find(node);
    if (it != rt->gc->cur_qjsvalues.end()) {
      return it->second;
    }
  }
  return 0;
}

size_t get_del_cnt(void *runtime, void *ptr) {
  LEPUSRuntime *rt = static_cast<LEPUSRuntime *>(runtime);
  std::unordered_map<void *, size_t>::iterator it = rt->gc->del_mems.find(ptr);
  if (it != rt->gc->del_mems.end()) {
    return it->second;
  }
  return 0;
}

void add_cur_mems(void *runtime, void *ptr) {
  if (!ptr) return;
  LEPUSRuntime *rt = static_cast<LEPUSRuntime *>(runtime);
  rt->gc->mem_order_cnt++;
  rt->gc->cur_mems[ptr] = rt->gc->mem_order_cnt;
  if (rt->gc->mem_order_cnt == INT_MAX) {
    std::cout << "add_cur_mems, ptr: " << ptr
              << " del_cnt: " << get_del_cnt(runtime, ptr) << std::endl;
  }
}

void delete_cur_mems(void *runtime, void *ptr) {
  LEPUSRuntime *rt = static_cast<LEPUSRuntime *>(runtime);
  rt->gc->del_mems[ptr] = get_cur_cnt(runtime, ptr);
  rt->gc->cur_mems.erase(ptr);
}

void multi_delete_cur_mems(void *runtime, void *ptr, int local_idx) {
  LEPUSRuntime *rt = static_cast<LEPUSRuntime *>(runtime);
  rt->gc->delete_mems[local_idx].insert(ptr);
}

void merge_mems(void *runtime) {
  LEPUSRuntime *rt = static_cast<LEPUSRuntime *>(runtime);
  for (int i = 0; i < THREAD_NUM; i++) {
    for (auto it : rt->gc->delete_mems[i]) {
      rt->gc->del_mems[it] = get_cur_cnt(runtime, it);
      rt->gc->cur_mems.erase(it);
    }
    rt->gc->delete_mems[i].clear();
  }
}

__attribute__((unused)) size_t get_cur_cnt(void *runtime, void *ptr) {
  LEPUSRuntime *rt = static_cast<LEPUSRuntime *>(runtime);
  std::unordered_map<void *, size_t>::iterator it = rt->gc->cur_mems.find(ptr);
  if (it != rt->gc->cur_mems.end()) {
    return it->second;
  }
  return 0;
}

bool check_valid_ptr(void *runtime, void *ptr) {
  if (get_cur_cnt(runtime, ptr) != 0) {
    return true;
  }
  return false;
}
#endif

QJS_STATIC inline BOOL __JS_AtomIsConst(JSAtom v) {
#if defined(DUMP_LEAKS) && DUMP_LEAKS > 1
  return (int32_t)v <= 0;
#else
  return (int32_t)v < JS_ATOM_END;
#endif
}
#ifdef ENABLE_COMPATIBLE_MM
static const int NUM_OF_LEPUSREF = 20000;
static const int UPDATE_GC_INFO_TIMES = 1;
static pthread_mutex_t runtime_mutex = PTHREAD_MUTEX_INITIALIZER;

static std::unordered_set<LEPUSRuntime *> *js_get_rt_set() {
  static std::unordered_set<LEPUSRuntime *> *g_rt_set =
      new std::unordered_set<LEPUSRuntime *>();
  return g_rt_set;
}

// <Primjs begin>
#ifdef ENABLE_LEPUSNG
static __attribute__((unused)) void JS_FreeStringCache(LEPUSRuntime *rt,
                                                       JSString *p);
#endif
// <Primjs end>
static LEPUSValue js_call_c_function(LEPUSContext *ctx,
                                     LEPUSValueConst func_obj,
                                     LEPUSValueConst this_obj, int argc,
                                     LEPUSValueConst *argv, int flags);
static LEPUSValue js_call_bound_function(LEPUSContext *ctx,
                                         LEPUSValueConst func_obj,
                                         LEPUSValueConst this_obj, int argc,
                                         LEPUSValueConst *argv, int flags);
static LEPUSValue JS_InvokeFree(LEPUSContext *ctx, LEPUSValue this_val,
                                JSAtom atom, int argc, LEPUSValueConst *argv);
static __exception int JS_ToArrayLengthFree(LEPUSContext *ctx, uint32_t *plen,
                                            LEPUSValue val, BOOL is_array_ctor);
static LEPUSValue JS_EvalObject(LEPUSContext *ctx, LEPUSValueConst this_obj,
                                LEPUSValueConst val, int flags, int scope_idx);

// <Primjs begin>
#if defined(DUMP_QJS_VALUE)
static __attribute__((unused)) void JS_DumpAtoms(LEPUSRuntime *rt);
static __attribute__((unused)) void JS_DumpString(LEPUSRuntime *rt,
                                                  const JSString *p);
static __attribute__((unused)) void JS_DumpStringNoPrint(LEPUSRuntime *rt,
                                                         const JSString *p,
                                                         char dump_buf[]);
static __attribute__((unused)) void JS_DumpObjectHeader(LEPUSRuntime *rt);
static __attribute__((unused)) void JS_DumpObject(LEPUSRuntime *rt,
                                                  LEPUSObject *p);
static __attribute__((unused)) void JS_DumpValueShort(LEPUSRuntime *rt,
                                                      LEPUSValueConst val);
static __attribute__((unused)) void JS_DumpValueShortNoPrint(
    LEPUSRuntime *rt, LEPUSValueConst val, char dump_buf[]);
static __attribute__((unused)) void JS_DumpValue(LEPUSContext *ctx,
                                                 LEPUSValueConst val);
static __attribute__((unused)) void JS_PrintValue(LEPUSContext *ctx,
                                                  const char *str,
                                                  LEPUSValueConst val);
static __attribute__((unused)) void JS_DumpShapes(LEPUSRuntime *rt);
#endif  // DUMP_QJS_VALUE
// <Primjs end>

static void js_proxy_finalizer(LEPUSRuntime *rt, LEPUSValue val);
static void js_proxy_mark(LEPUSRuntime *rt, LEPUSValueConst val,
                          LEPUS_MarkFunc *mark_func, int local_idx);
static void js_weakref_finalizer(LEPUSRuntime *rt, LEPUSValue val);
static void js_finalizationRegistry_finalizer(LEPUSRuntime *rt, LEPUSValue val);
static void js_finalizationRegistry_mark(LEPUSRuntime *rt, LEPUSValueConst val,
                                         LEPUS_MarkFunc *mark_func,
                                         int local_idx);
static LEPUSValue js_promise_resolve_function_call(
    LEPUSContext *ctx, LEPUSValueConst func_obj, LEPUSValueConst this_val,
    int argc, LEPUSValueConst *argv, int flags);
static LEPUSValue JS_ToStringFree(LEPUSContext *ctx, LEPUSValue val);
static int JS_ToInt32Free(LEPUSContext *ctx, int32_t *pres, LEPUSValue val);
static int JS_ToFloat64Free(LEPUSContext *ctx, double *pres, LEPUSValue val);
static int JS_ToUint8ClampFree(LEPUSContext *ctx, int32_t *pres,
                               LEPUSValue val);
static LEPUSValue js_compile_regexp(LEPUSContext *ctx, LEPUSValueConst pattern,
                                    LEPUSValueConst flags);

typedef enum JSStrictEqModeEnum {
  JS_EQ_STRICT,
  JS_EQ_SAME_VALUE,
  JS_EQ_SAME_VALUE_ZERO,
} JSStrictEqModeEnum;

static BOOL js_strict_eq2(LEPUSContext *ctx, LEPUSValue op1, LEPUSValue op2,
                          JSStrictEqModeEnum eq_mode);
static BOOL js_strict_eq(LEPUSContext *ctx, LEPUSValue op1, LEPUSValue op2);
static BOOL js_same_value(LEPUSContext *ctx, LEPUSValueConst op1,
                          LEPUSValueConst op2);
static BOOL js_same_value_zero(LEPUSContext *ctx, LEPUSValueConst op1,
                               LEPUSValueConst op2);
static LEPUSValue JS_ToObjectFree(LEPUSContext *ctx, LEPUSValue val);
static LEPUSValue JS_ThrowTypeErrorRevokedProxy(LEPUSContext *ctx);
static LEPUSValue js_proxy_call(LEPUSContext *ctx, LEPUSValueConst func_obj,
                                LEPUSValueConst this_obj, int argc,
                                LEPUSValueConst *argv, int flags);
static LEPUSValue js_proxy_call_constructor(LEPUSContext *ctx,
                                            LEPUSValueConst func_obj,
                                            LEPUSValueConst new_target,
                                            int argc, LEPUSValueConst *argv);
static LEPUSValueConst js_proxy_getPrototypeOf(LEPUSContext *ctx,
                                               LEPUSValueConst obj);
static int js_proxy_setPrototypeOf(LEPUSContext *ctx, LEPUSValueConst obj,
                                   LEPUSValueConst proto_val, BOOL throw_flag);
static int js_proxy_isExtensible(LEPUSContext *ctx, LEPUSValueConst obj);
static int js_proxy_preventExtensions(LEPUSContext *ctx, LEPUSValueConst obj);
static int js_proxy_isArray(LEPUSContext *ctx, LEPUSValueConst obj);
static int JS_CreateProperty(LEPUSContext *ctx, LEPUSObject *p, JSAtom prop,
                             LEPUSValueConst val, LEPUSValueConst getter,
                             LEPUSValueConst setter, int flags);
static uint32_t typed_array_get_length(LEPUSContext *ctx, LEPUSObject *p);
static LEPUSValue JS_ThrowTypeErrorDetachedArrayBuffer(LEPUSContext *ctx);

static LEPUSValue js_generator_function_call(LEPUSContext *ctx,
                                             LEPUSValueConst func_obj,
                                             LEPUSValueConst this_obj, int argc,
                                             LEPUSValueConst *argv, int flags);
static LEPUSValue js_new_promise_capability(LEPUSContext *ctx,
                                            LEPUSValue *resolving_funcs,
                                            LEPUSValueConst ctor);
class ObjectCloneStateGC;
static __exception int perform_promise_then(
    LEPUSContext *ctx, LEPUSValueConst promise, LEPUSValueConst *resolve_reject,
    LEPUSValueConst *cap_resolving_funcs, ObjectCloneStateGC *state = NULL);
static LEPUSValue js_promise_resolve(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv, int magic);
static LEPUSValue JS_ToNumber(LEPUSContext *ctx, LEPUSValueConst val);
static int JS_NumberIsInteger(LEPUSContext *ctx, LEPUSValueConst val);
static BOOL JS_NumberIsNegativeOrMinusZero(LEPUSContext *ctx,
                                           LEPUSValueConst val);
static LEPUSValue JS_ToNumberFree(LEPUSContext *ctx, LEPUSValue val);
static int JS_GetOwnPropertyInternal(LEPUSContext *ctx,
                                     LEPUSPropertyDescriptor *desc,
                                     LEPUSObject *p, JSAtom prop);
QJS_STATIC void JS_AddIntrinsicBasicObjects_GC(LEPUSContext *ctx);
static void js_free_shape(LEPUSRuntime *rt, JSShape *sh);
static void js_free_shape_null(LEPUSRuntime *rt, JSShape *sh);
static int js_shape_prepare_update(LEPUSContext *ctx, LEPUSObject *p,
                                   JSShapeProperty **pprs);
static int init_shape_hash(LEPUSRuntime *rt);
__exception int js_get_length32_gc(LEPUSContext *ctx, uint32_t *pres,
                                   LEPUSValueConst obj);
static __exception int js_get_length64(LEPUSContext *ctx, int64_t *pres,
                                       LEPUSValueConst obj);
static LEPUSValue *build_arg_list(LEPUSContext *ctx, uint32_t *plen,
                                  LEPUSValueConst array_arg);
static BOOL js_get_fast_array(LEPUSContext *ctx, LEPUSValueConst obj,
                              LEPUSValue **arrpp, uint32_t *countp);
static LEPUSValue js_c_function_data_call(LEPUSContext *ctx,
                                          LEPUSValueConst func_obj,
                                          LEPUSValueConst this_val, int argc,
                                          LEPUSValueConst *argv, int flags);

static int js_module_ns_has(LEPUSContext *ctx, LEPUSValueConst obj,
                            JSAtom atom);
static int js_proxy_get_own_property(LEPUSContext *ctx,
                                     LEPUSPropertyDescriptor *pdesc,
                                     LEPUSValueConst obj, JSAtom prop);
static int js_proxy_define_own_property(LEPUSContext *ctx, LEPUSValueConst obj,
                                        JSAtom prop, LEPUSValueConst val,
                                        LEPUSValueConst getter,
                                        LEPUSValueConst setter, int flags);
static int js_proxy_delete_property(LEPUSContext *ctx, LEPUSValueConst obj,
                                    JSAtom atom);
static int js_proxy_get_own_property_names(LEPUSContext *ctx,
                                           LEPUSPropertyEnum **ptab,
                                           uint32_t *plen, LEPUSValueConst obj);
static int js_proxy_has(LEPUSContext *ctx, LEPUSValueConst obj, JSAtom atom);
static LEPUSValue js_proxy_get(LEPUSContext *ctx, LEPUSValueConst obj,
                               JSAtom atom, LEPUSValueConst receiver);
static int js_proxy_set(LEPUSContext *ctx, LEPUSValueConst obj, JSAtom atom,
                        LEPUSValueConst value, LEPUSValueConst receiver,
                        int flags);
static int js_string_get_own_property(LEPUSContext *ctx,
                                      LEPUSPropertyDescriptor *desc,
                                      LEPUSValueConst obj, JSAtom prop);
static int js_string_define_own_property(LEPUSContext *ctx,
                                         LEPUSValueConst this_obj, JSAtom prop,
                                         LEPUSValueConst val,
                                         LEPUSValueConst getter,
                                         LEPUSValueConst setter, int flags);
static int js_string_delete_property(LEPUSContext *ctx, LEPUSValueConst obj,
                                     JSAtom prop);
static int js_arguments_define_own_property(LEPUSContext *ctx,
                                            LEPUSValueConst this_obj,
                                            JSAtom prop, LEPUSValueConst val,
                                            LEPUSValueConst getter,
                                            LEPUSValueConst setter, int flags);
static int js_string_get_own_property_names(LEPUSContext *ctx,
                                            LEPUSPropertyEnum **ptab,
                                            uint32_t *plen,
                                            LEPUSValueConst obj);

static const LEPUSClassExoticMethods js_arguments_exotic_methods = {
    .define_own_property = js_arguments_define_own_property,
};
static const LEPUSClassExoticMethods js_string_exotic_methods = {
    .get_own_property = js_string_get_own_property,
    .get_own_property_names = js_string_get_own_property_names,
    .define_own_property = js_string_define_own_property,
    .delete_property = js_string_delete_property,
};
static const LEPUSClassExoticMethods js_proxy_exotic_methods = {
    .get_own_property = js_proxy_get_own_property,
    .define_own_property = js_proxy_define_own_property,
    .delete_property = js_proxy_delete_property,
    .get_own_property_names = js_proxy_get_own_property_names,
    .has_property = js_proxy_has,
    .get_property = js_proxy_get,
    .set_property = js_proxy_set,
};
static const LEPUSClassExoticMethods js_module_ns_exotic_methods = {
    .has_property = js_module_ns_has,
};

static LEPUSValue js_instantiate_prototype(LEPUSContext *ctx, LEPUSObject *p,
                                           JSAtom atom, void *opaque);
static LEPUSValue JS_InstantiateFunctionListItem2(LEPUSContext *ctx,
                                                  LEPUSObject *p, JSAtom atom,
                                                  void *opaque);

static LEPUSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT;
// <Primjs begin>

static LEPUSValue JSRef2Value(LEPUSContext *ctx, LEPUSValue ref) {
#ifdef ENABLE_LEPUSNG
  if (LEPUS_IsLepusRef(ref)) {
    auto *js_ref = static_cast<LEPUSLepusRef *>(LEPUS_VALUE_GET_PTR(ref));
    auto &obj = js_ref->lepus_val;
    if (LEPUS_VALUE_IS_OBJECT(obj)) {
      return obj;
    }
    return obj = ctx->rt->js_callbacks_.convert_to_object(ctx, ref);
  }
#endif
  return ref;
}

// <Primjs end>

static size_t js_malloc_usable_size_unknown(const void *ptr) { return 0; }

QJS_STATIC inline void js_dbuf_init(LEPUSContext *ctx, DynBuf *s) {
  dbuf_init2(s, ctx->rt,
             reinterpret_cast<DynBufReallocFunc *>(lepus_realloc_rt));
}

static JSClassShortDef const js_std_class_def[] = {
    {JS_ATOM_Object, NULL, NULL},            /* JS_CLASS_OBJECT */
    {JS_ATOM_Array, NULL, NULL},             /* JS_CLASS_ARRAY */
    {JS_ATOM_Error, NULL, NULL},             /* JS_CLASS_ERROR */
    {JS_ATOM_Number, NULL, NULL},            /* JS_CLASS_NUMBER */
    {JS_ATOM_String, NULL, NULL},            /* JS_CLASS_STRING */
    {JS_ATOM_Boolean, NULL, NULL},           /* JS_CLASS_BOOLEAN */
    {JS_ATOM_Symbol, NULL, NULL},            /* JS_CLASS_SYMBOL */
    {JS_ATOM_Arguments, NULL, NULL},         /* JS_CLASS_ARGUMENTS */
    {JS_ATOM_Arguments, NULL, NULL},         /* JS_CLASS_MAPPED_ARGUMENTS */
    {JS_ATOM_Date, NULL, NULL},              /* JS_CLASS_DATE */
    {JS_ATOM_Object, NULL, NULL},            /* JS_CLASS_MODULE_NS */
    {JS_ATOM_Function, NULL, NULL},          /* JS_CLASS_C_FUNCTION */
    {JS_ATOM_Function, NULL, NULL},          /* JS_CLASS_BYTECODE_FUNCTION */
    {JS_ATOM_Function, NULL, NULL},          /* JS_CLASS_BOUND_FUNCTION */
    {JS_ATOM_Function, NULL, NULL},          /* JS_CLASS_C_FUNCTION_DATA */
    {JS_ATOM_GeneratorFunction, NULL, NULL}, /* JS_CLASS_GENERATOR_FUNCTION */
    {JS_ATOM_ForInIterator, NULL, NULL},     /* JS_CLASS_FOR_IN_ITERATOR */
    {JS_ATOM_RegExp, NULL, NULL},            /* JS_CLASS_REGEXP */
    {JS_ATOM_ArrayBuffer, NULL, NULL},       /* JS_CLASS_ARRAY_BUFFER */
    {JS_ATOM_SharedArrayBuffer, NULL, NULL}, /* JS_CLASS_SHARED_ARRAY_BUFFER */
    {JS_ATOM_Uint8ClampedArray, NULL, NULL}, /* JS_CLASS_UINT8C_ARRAY */
    {JS_ATOM_Int8Array, NULL, NULL},         /* JS_CLASS_INT8_ARRAY */
    {JS_ATOM_Uint8Array, NULL, NULL},        /* JS_CLASS_UINT8_ARRAY */
    {JS_ATOM_Int16Array, NULL, NULL},        /* JS_CLASS_INT16_ARRAY */
    {JS_ATOM_Uint16Array, NULL, NULL},       /* JS_CLASS_UINT16_ARRAY */
    {JS_ATOM_Int32Array, NULL, NULL},        /* JS_CLASS_INT32_ARRAY */
    {JS_ATOM_Uint32Array, NULL, NULL},       /* JS_CLASS_UINT32_ARRAY */
    {JS_ATOM_Float32Array, NULL, NULL},      /* JS_CLASS_FLOAT32_ARRAY */
    {JS_ATOM_Float64Array, NULL, NULL},      /* JS_CLASS_FLOAT64_ARRAY */
    {JS_ATOM_null, NULL, NULL},              /* JS_CLASS_BIG_INT64_ARRAY */
    {JS_ATOM_null, NULL, NULL},              /* JS_CLASS_BIG_UINT64_ARRAY */
    {JS_ATOM_DataView, NULL, NULL},          /* JS_CLASS_DATAVIEW */
    {JS_ATOM_null, NULL, NULL},              /* JS_CLASS_BIG_INT */
    {JS_ATOM_Map, NULL, NULL},               /* JS_CLASS_MAP */
    {JS_ATOM_Set, NULL, NULL},               /* JS_CLASS_SET */
    {JS_ATOM_WeakMap, NULL, NULL},           /* JS_CLASS_WEAKMAP */
    {JS_ATOM_WeakSet, NULL, NULL},           /* JS_CLASS_WEAKSET */
    {JS_ATOM_Map_Iterator, NULL, NULL},      /* JS_CLASS_MAP_ITERATOR */
    {JS_ATOM_Set_Iterator, NULL, NULL},      /* JS_CLASS_SET_ITERATOR */
    {JS_ATOM_Array_Iterator, NULL, NULL},    /* JS_CLASS_ARRAY_ITERATOR */
    {JS_ATOM_String_Iterator, NULL, NULL},   /* JS_CLASS_STRING_ITERATOR */
    {JS_ATOM_RegExp_String_Iterator, NULL, NULL}, /* JS_CLASS_STRING_ITERATOR */
    {JS_ATOM_Generator, NULL, NULL},              /* JS_CLASS_GENERATOR */
};

static inline uint8_t *js_get_stack_pointer(void);
LEPUSRuntime *JS_NewRuntime2_GC(const LEPUSMallocFunctions *mf, void *opaque,
                                uint32_t mode) {
  LEPUSRuntime *rt;
  JSMallocState ms;

  memset(&ms, 0, sizeof(ms));
  ms.opaque = opaque;
  ms.malloc_limit = -1;

  const char *module_name = MODULE_PRIMJS;
  MonitorEvent(MODULE_QUICK, DEFAULT_BIZ_NAME, "NewRuntime", module_name);

  rt = static_cast<LEPUSRuntime *>(system_malloc(sizeof(LEPUSRuntime)));
  if (!rt) return NULL;
  memset(rt, 0, sizeof(*rt));
  rt->gc_enable = true;
  rt->use_primjs = true;
  rt->init_time = get_daytime();
  rt->mf = *mf;
  if (!rt->mf.lepus_malloc_usable_size) {
    /* use dummy function if none provided */
    rt->mf.lepus_malloc_usable_size = js_malloc_usable_size_unknown;
  }
  rt->malloc_state = ms;
  rt->workerThreadPool = nullptr;
  rt->malloc_state.allocate_state.mflags = 1;
  rt->malloc_state.allocate_state.runtime = static_cast<void *>(rt);
  rt->malloc_state.allocate_state.mtx = PTHREAD_MUTEX_INITIALIZER;
  rt->malloc_state.allocate_state.smallbins = static_cast<mchunkptr *>(
      system_mallocz(sizeof(mchunkptr) * ((NSMALLBINS + 1) * 2)));
  rt->malloc_state.allocate_state.treebins =
      static_cast<tbinptr *>(system_mallocz(sizeof(tbinptr) * NTREEBINS));
  for (int i = 0; i < THREAD_NUM; i++) {
    rt->malloc_state.allocate_state.local_smallbins[i] =
        static_cast<mchunkptr *>(
            system_mallocz(sizeof(mchunkptr) * ((NSMALLBINS + 1) * 2)));
    rt->malloc_state.allocate_state.local_treebins[i] =
        static_cast<tbinptr *>(system_mallocz(sizeof(tbinptr) * NTREEBINS));
  }
#if defined(ANDROID) || defined(__ANDROID__) || defined(OS_IOS)
  rt->malloc_state.allocate_state.footprint_limit = 8 * MB;
  strcpy(rt->malloc_state.allocate_state.mem_name, "quickjs_heap_");
#else
  rt->malloc_state.allocate_state.footprint_limit = 64 * MB;
#endif
  set_gc_info_threshold(&rt->malloc_state.allocate_state, mode);

  /* for trace gc */
  rt->ptr_handles = new PtrHandles(rt);
  rt->gc = new GarbageCollector(rt, &(rt->malloc_state.allocate_state));
  rt->global_handles_ = new GlobalHandles(rt);
  rt->qjsvaluevalue_allocator = new QJSValueValueSpace(rt);
  rt->gc_cnt = 0;
  rt->malloc_gc_threshold = 256 * 1024;

  init_list_head(&rt->context_list);
#ifdef DUMP_LEAKS
  init_list_head(&rt->string_list);
#endif
  init_list_head(&rt->job_list);
  init_list_head(&rt->unhandled_rejections);

#if defined(__aarch64__) && (defined(ANDROID) || defined(__ANDROID__)) && \
    !DISABLE_NANBOX
  HEAP_TAG_OUTER = (int64_t)rt & LEPUS_PTR_TAG;
  void *ptr = lepus_malloc_rt(rt, 1, ALLOC_TAG_WITHOUT_PTR);
  HEAP_TAG_INNER = (int64_t)ptr & LEPUS_PTR_TAG;
  LOGI("GC: HEAP_TAG_OUTER: %p, HEAP_TAG_INNER: %p",
       reinterpret_cast<void *>(HEAP_TAG_OUTER),
       reinterpret_cast<void *>(HEAP_TAG_INNER));
#endif

#ifndef ENABLE_FORCE_GC
  rt->mem_for_oom = nullptr;
#else
  rt->mem_for_oom = lepus_malloc_rt(rt, 16 * KB, ALLOC_TAG_WITHOUT_PTR);
#endif

  if (JS_InitAtoms(rt)) goto fail;

  /* create the object, array and function classes */
  if (init_class_range(rt, js_std_class_def, JS_CLASS_OBJECT,
                       countof(js_std_class_def)) < 0)
    goto fail;
  init_bigint_name(rt);
  rt->class_array[JS_CLASS_ARGUMENTS].exotic = &js_arguments_exotic_methods;
  rt->class_array[JS_CLASS_STRING].exotic = &js_string_exotic_methods;
  rt->class_array[JS_CLASS_MODULE_NS].exotic = &js_module_ns_exotic_methods;

  rt->class_array[JS_CLASS_C_FUNCTION].call = js_call_c_function;
  rt->class_array[JS_CLASS_C_FUNCTION_DATA].call = js_c_function_data_call;
  rt->class_array[JS_CLASS_BOUND_FUNCTION].call = js_call_bound_function;
  rt->class_array[JS_CLASS_GENERATOR_FUNCTION].call =
      js_generator_function_call;
  // <Primjs begin>
#ifdef ENABLE_LEPUSNG
  rt->js_type_.array_typeid_ = -1;
  rt->js_type_.table_typeid_ = -1;
#endif
  // <Primjs end>
  if (init_shape_hash(rt)) goto fail;

#ifdef BUILD_ASYNC_STACK
  rt->current_micro_task = NULL;
#endif
  rt->stack_top = js_get_stack_pointer();
  rt->stack_size = LEPUS_DEFAULT_STACK_SIZE;
  rt->current_exception = LEPUS_NULL;

  // Primjs begin
  js_init_settings_options(rt);
  // Primjs end

  pthread_mutex_lock(&runtime_mutex);
  js_get_rt_set()->insert(rt);
  pthread_mutex_unlock(&runtime_mutex);

  return rt;
fail:
  JS_FreeRuntime_GC(rt);
  return NULL;
}

void add_footprint_limit(struct malloc_state *m, size_t size) {
  LEPUSRuntime *rt = static_cast<LEPUSRuntime *>(m->runtime);
  size_t max_limit = rt->gc->GetMaxLimit();
  size_t new_limit = m->footprint_limit + size;
  if (max_limit != 0 && max_limit < new_limit) {
    m->footprint_limit = max_limit;
  } else {
    m->footprint_limit = new_limit;
  }
}

void JS_SetGCPauseSuppressionMode_GC(LEPUSRuntime *rt, bool mode) {
  rt->gc->SetGCPauseSuppressionMode(mode);
}

bool JS_GetGCPauseSuppressionMode_GC(LEPUSRuntime *rt) {
  return rt->gc->GetGCPauseSuppressionMode();
}

/* default memory allocation functions with memory limitation */
QJS_STATIC inline size_t js_def_malloc_usable_size(void *ptr) {
#if defined(__APPLE__)
  return malloc_size(ptr);
#elif defined(_WIN32)
  return _msize(ptr);
#elif defined(EMSCRIPTEN)
  return 0;
#elif defined(__linux__)
  return malloc_usable_size(ptr);
#else
  /* change this to `return 0;` if
   * compilation fails */
  return malloc_usable_size(ptr);
#endif
}

static void *js_def_malloc(JSMallocState *s, size_t size, int alloc_tag) {
  void *ptr;

  /* Do not allocate zero bytes: behavior is platform dependent */
  assert(size != 0);

  if (unlikely(s->malloc_size + size > s->malloc_limit)) return NULL;

  ptr = malloc(size);
  if (!ptr) return NULL;

  s->malloc_count++;
  s->malloc_size += js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
  return ptr;
}

static __attribute__((unused)) void change_to_local_idx(struct malloc_state *m,
                                                        int local_idx) {
  // smallmap
  binmap_t tmp_map = m->smallmap;
  m->smallmap = m->local_smallmap[local_idx];
  m->local_smallmap[local_idx] = tmp_map;
  // treemap
  tmp_map = m->treemap;
  m->treemap = m->local_treemap[local_idx];
  m->local_treemap[local_idx] = tmp_map;
  // smallbins
  mchunkptr *tmp_smallbins = m->smallbins;
  m->smallbins = m->local_smallbins[local_idx];
  m->local_smallbins[local_idx] = tmp_smallbins;
  // treebins
  tbinptr *tmp_tbins = m->treebins;
  m->treebins = m->local_treebins[local_idx];
  m->local_treebins[local_idx] = tmp_tbins;
}

bool switch_local_idx(struct malloc_state *m, size_t size) {
  for (int i = 0; i < THREAD_NUM; i++) {
    if (m->gc_flag[i] == 1) {
      change_to_local_idx(m, i);
      m->gc_flag[i] = 0;
#if defined(ANDROID) || defined(__ANDROID__)
      void *ptr = allocate(m, size);
      if (ptr) {
        gcfree(m, ptr);
        return true;
      } else {
        add_footprint_limit(m, size * 1.5);
      }
#else
      add_footprint_limit(m, size * 1.5);
      return true;
#endif
    }
  }
  return false;
}

void trig_gc(JSMallocState *s, size_t size, bool is_outer) {
  struct malloc_state *m = &s->allocate_state;
  LEPUSRuntime *rt = static_cast<LEPUSRuntime *>(m->runtime);
  if (is_outer) {
    rt->gc->CollectGarbage(size);
    return;
  }
  if (switch_local_idx(m, size)) {
    return;
  }
  // gc pause suppression mode
  if (rt->gc->GetGCPauseSuppressionMode() && rt->is_lepusng &&
      m->footprint < 240 * MB) {
    size_t expand_size = size * 1.5;
    if (expand_size < 5 * MB) {
      expand_size = 5 * MB;
    }
    add_footprint_limit(m, expand_size);
    return;
  }
  rt->gc->CollectGarbage(size);
  if (switch_local_idx(m, size)) {
    return;
  }
}

#define JS_OBJECT_IS_ARRAY_BUFFER(obj)       \
  (obj->class_id == JS_CLASS_ARRAY_BUFFER || \
   obj->class_id == JS_CLASS_SHARED_ARRAY_BUFFER)

static void *base_allocate(JSMallocState *s, size_t size, int alloc_tag,
                           void *(*allocate_debug)(struct malloc_state *state,
                                                   size_t size),
                           size_t (*allocate_usable_size)(void *ptr)) {
#ifdef ENABLE_FORCE_GC
  LEPUS_RunGC(static_cast<LEPUSRuntime *>(s->allocate_state.runtime));
#endif
  JS_UpdateGCInfo(s, size);
  void *ptr;

  /* Do not allocate zero bytes: behavior is platform dependent */
  assert(size != 0);

  ptr = allocate_debug(&s->allocate_state, size);

  if (!ptr) {
    trig_gc(s, size);
    ptr = allocate_debug(&s->allocate_state, size);
  }

  if (!ptr) {
#ifdef __ANDROID__
    __android_log_print(ANDROID_LOG_ERROR, "PRIMJS_GC",
                        "trace_gc_error, OOM, alloc_size: %zu, "
                        "footprint_limit: %zu, cur_malloc_size: %zu",
                        size, s->allocate_state.footprint_limit,
                        s->allocate_state.cur_malloc_size);
#else
    printf("trace_gc_error, OOM, alloc_size: %zu\n", size);
#endif
    LEPUSRuntime *rt = static_cast<LEPUSRuntime *>(s->allocate_state.runtime);
    if (rt->mem_for_oom != nullptr) {
      gcfree(&s->allocate_state, rt->mem_for_oom);
      rt->mem_for_oom = nullptr;
    }
    return nullptr;
  }
  *(reinterpret_cast<int *>(ptr) - 2) = alloc_tag;
#ifdef ENABLE_GC_DEBUG_TOOLS
  // DCHECK(alloc_tag != 0);
#endif
  return ptr;
}

static void *js_def_allocate(JSMallocState *s, size_t size, int alloc_tag) {
  return base_allocate(s, size, alloc_tag, allocate, allocate_usable_size);
}

static void *js_def_allocate_gc(JSMallocState *s, size_t size, int alloc_tag) {
  void *ptr = base_allocate(s, size, alloc_tag, allocate, allocate_usable_size);
  LEPUSRuntime *rt = static_cast<LEPUSRuntime *>(s->allocate_state.runtime);
  rt->gc->GetVisitor()->AddObjectDuringGC(ptr);
  return ptr;
}

static void js_def_free(JSMallocState *s, void *ptr) {
  if (!ptr) return;

  s->malloc_count--;
  s->malloc_size -= js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
  free(ptr);
}

static void base_gcfree(JSMallocState *s, void *ptr,
                        size_t (*allocate_usable_size)(void *ptr),
                        void (*cur_free)(struct malloc_state *state,
                                         void *ptr)) {
  if (!ptr) return;
  s->malloc_count--;
  s->malloc_size -=
      (uint64_t)allocate_usable_size(ptr) + (uint64_t)MALLOC_OVERHEAD;
}

static void js_def_gcfree(JSMallocState *s, void *ptr) {
  base_gcfree(s, ptr, allocate_usable_size, gcfree);
}

static void *js_def_realloc(JSMallocState *s, void *ptr, size_t size,
                            int alloc_tag) {
  size_t old_size;

  if (!ptr) {
    if (size == 0) return NULL;
    return js_def_malloc(s, size, alloc_tag);
  }
  old_size = js_def_malloc_usable_size(ptr);
  if (size == 0) {
    s->malloc_count--;
    s->malloc_size -= old_size + MALLOC_OVERHEAD;
    free(ptr);
    return NULL;
  }
  if (s->malloc_size + size - old_size > s->malloc_limit) return NULL;

  ptr = realloc(ptr, size);
  if (!ptr) return NULL;

  s->malloc_size += js_def_malloc_usable_size(ptr) - old_size;
  return ptr;
}

static void *base_reallocate(
    JSMallocState *s, void *ptr, size_t size, int alloc_tag,
    void *(*allocate_debug)(JSMallocState *s, size_t size, int alloc_tag),
    size_t (*allocate_usable_size)(void *ptr),
    void *(*cur_realloc)(struct malloc_state *s, void *ptr, size_t size),
    void (*cur_free)(struct malloc_state *state, void *ptr)) {
#ifdef ENABLE_FORCE_GC
  LEPUS_RunGC(static_cast<LEPUSRuntime *>(s->allocate_state.runtime));
#endif
  mstate m = &s->allocate_state;
  JS_UpdateGCInfo(s, size);
  void *new_ptr;

  if (!ptr) {
    if (size == 0) return NULL;
    new_ptr = allocate_debug(s, size, alloc_tag);
    if (!new_ptr) {
      trig_gc(s, size);
      new_ptr = allocate_debug(s, size, alloc_tag);
    }
    if (!new_ptr) {
#ifdef __ANDROID__
      __android_log_print(ANDROID_LOG_ERROR, "PRIMJS_GC",
                          "trace_gc_error, OOM, alloc_size: %zu, "
                          "footprint_limit: %zu, cur_malloc_size: %zu",
                          size, s->allocate_state.footprint_limit,
                          s->allocate_state.cur_malloc_size);
#else
      printf("trace_gc_error, OOM, alloc_size: %zu\n", size);
#endif
      LEPUSRuntime *rt = static_cast<LEPUSRuntime *>(s->allocate_state.runtime);
      if (rt->mem_for_oom != nullptr) {
        gcfree(&s->allocate_state, rt->mem_for_oom);
        rt->mem_for_oom = nullptr;
      }
      return nullptr;
    }
    return new_ptr;
  }
  if (size == 0) {
    return NULL;
  }
  new_ptr = cur_realloc(&s->allocate_state, ptr, size);
  if (!new_ptr) {
    trig_gc(s, size);
    new_ptr = cur_realloc(&s->allocate_state, ptr, size);
  }
  if (!new_ptr) {
#ifdef __ANDROID__
    __android_log_print(ANDROID_LOG_ERROR, "PRIMJS_GC",
                        "trace_gc_error, OOM, alloc_size: %zu, "
                        "footprint_limit: %zu, cur_malloc_size: %zu",
                        size, s->allocate_state.footprint_limit,
                        s->allocate_state.cur_malloc_size);
#else
    printf("trace_gc_error, OOM, alloc_size: %zu\n", size);
#endif
    LEPUSRuntime *rt = static_cast<LEPUSRuntime *>(s->allocate_state.runtime);
    if (rt->mem_for_oom != nullptr) {
      gcfree(&s->allocate_state, rt->mem_for_oom);
      rt->mem_for_oom = nullptr;
    }
    return nullptr;
  }
  *(reinterpret_cast<int *>(new_ptr) - 2) = alloc_tag;
#ifdef ENABLE_GC_DEBUG_TOOLS
  // DCHECK(alloc_tag != 0);
#endif
  return new_ptr;
}

static void *js_def_reallocate(JSMallocState *s, void *ptr, size_t size,
                               int alloc_tag) {
  return base_reallocate(s, ptr, size, alloc_tag, js_def_allocate,
                         allocate_usable_size, reallocate, gcfree);
}

static void *js_def_reallocate_gc(JSMallocState *s, void *ptr, size_t size,
                                  int alloc_tag) {
  void *ret = base_reallocate(s, ptr, size, alloc_tag, js_def_allocate,
                              allocate_usable_size, reallocate, gcfree);
  LEPUSRuntime *rt = static_cast<LEPUSRuntime *>(s->allocate_state.runtime);
  rt->gc->GetVisitor()->AddObjectDuringGC(ret);
  return ret;
}

static const LEPUSMallocFunctions def_allocate_funcs = {
    js_def_allocate,
    js_def_gcfree,
    js_def_reallocate,
    (size_t(*)(const void *))allocate_usable_size,
};

LEPUSRuntime *JS_NewRuntime_GC(uint32_t mode) {
  return JS_NewRuntime2_GC(&def_allocate_funcs, NULL, mode);
}

void JS_SetMemoryLimit_GC(LEPUSRuntime *rt, size_t limit) {
  if (limit < rt->malloc_state.allocate_state.footprint_limit) {
    rt->malloc_state.allocate_state.footprint_limit = limit;
  }
  rt->gc->SetMaxLimit(limit);
}

/* return 0 if OK, < 0 if exception */
int JS_EnqueueJob_GC(LEPUSContext *ctx, LEPUSJobFunc *job_func, int argc,
                     LEPUSValueConst *argv) {
  LEPUSRuntime *rt = ctx->rt;
  JSJobEntry *e;
  int i;

  e = static_cast<JSJobEntry *>(lepus_malloc(
      ctx, sizeof(*e) + argc * sizeof(LEPUSValue), ALLOC_TAG_WITHOUT_PTR));
  if (!e) return -1;
  e->ctx = ctx;
  e->job_func = job_func;
  e->argc = argc;
  for (i = 0; i < argc; i++) {
    e->argv[i] = argv[i];
  }
  list_add_tail(&e->link, &rt->job_list);
  return 0;
}

/* return < 0 if exception, 0 if no job pending, 1 if a job was
   executed successfully. the context of the job is stored in '*pctx' */
int JS_ExecutePendingJob_GC(LEPUSRuntime *rt, LEPUSContext **pctx) {
  LEPUSContext *ctx;
  JSJobEntry *e;
  LEPUSValue res;
  int i, ret;

  if (list_empty(&rt->job_list)) {
    *pctx = NULL;
    return 0;
  }

  /* get the first pending job and execute it */
  e = list_entry(rt->job_list.next, JSJobEntry, link);
  list_del(&e->link);
  HandleScope func_scope(rt);
  func_scope.PushHandle(e, HANDLE_TYPE_DIR_HEAP_OBJ);
  for (i = 0; i < e->argc; i++) {
    func_scope.PushHandle(&e->argv[i], HANDLE_TYPE_LEPUS_VALUE);
  }
  ctx = e->ctx;
  res = e->job_func(e->ctx, e->argc,
                    reinterpret_cast<LEPUSValueConst *>(e->argv));
  if (LEPUS_IsException(res))
    ret = -1;
  else
    ret = 1;
  *pctx = ctx;
  return ret;
}

QJS_STATIC inline uint32_t atom_get_free(const JSAtomStruct *p) {
  return (uintptr_t)p >> 1;
}

QJS_STATIC inline JSAtomStruct *atom_set_free(uint32_t v) {
  return reinterpret_cast<JSAtomStruct *>(((uintptr_t)v << 1) | 1);
}

void JS_FreeRuntime_GC(LEPUSRuntime *rt) {
  pthread_mutex_lock(&runtime_mutex);
  std::unordered_set<LEPUSRuntime *> *g_rt_set = js_get_rt_set();
  if (g_rt_set->find(rt) != g_rt_set->end()) g_rt_set->erase(rt);
  pthread_mutex_unlock(&runtime_mutex);
  rt->gc->DoOnlyFinalizer();
#if defined(ENABLE_GC_DEBUG_TOOLS) && (defined(ANDROID) || defined(__ANDROID__))
  __android_log_print(
      ANDROID_LOG_ERROR, "PRIMJS_GC",
      "free_runtime, leak_handle_num: %zu, leak_qjsvalue_num: %zu, rt: %p\n",
      rt->gc->GetHandleSize(), rt->gc->GetQjsValueSize(), rt);
#endif

  struct list_head *el, *el1;
  int i;

  list_for_each_safe(el, el1, &rt->context_list) {
    LEPUSContext *ctx = list_entry(el, LEPUSContext, link);
    JS_FreeContext_GC(ctx);
  }
  init_list_head(&rt->context_list);

  init_list_head(&rt->job_list);

  init_list_head(&rt->unhandled_rejections);

  /* free the classes */
  rt->class_count = 0;
  rt->class_array = NULL;

  rt->current_exception = LEPUS_NULL;

  /* free the atoms */
#ifdef ENABLE_LEPUSNG
  for (int i = 0; i < rt->atom_size; i++) {
    JSAtomStruct *p = rt->atom_array[i];
    if (!atom_is_free(p)) {
      JS_FreeStringCache(rt, p);
    }
  }
#endif
  rt->atom_size = 0;
  rt->atom_array = NULL;
  rt->atom_hash = NULL;
  rt->shape_hash = NULL;
  rt->mem_for_oom = NULL;

#ifdef ENABLE_TRACING_GC_LOG
  // rt->gc->CollectGarbage();
#endif

#ifdef ENABLE_GC_DEBUG_TOOLS
#if 0
  rt->gc->CollectGarbage();
  if (!rt->gc->cur_mems.empty()) {
    std::cout << "trace_gc, leak_cnt: " << rt->gc->cur_mems.size() << std::endl;
    for (std::unordered_map<void *, size_t>::iterator it =
             rt->gc->cur_mems.begin();
         it != rt->gc->cur_mems.end(); it++) {
      std::cout << "trace_gc_leak, ptr: " << it->first
                << " mem_cnt: " << it->second
                << " size: " << allocate_usable_size(it->first) << std::endl;
    }
  }
#endif
#endif

  // free trace_gc data
  if (rt->ptr_handles) {
    delete rt->ptr_handles;
    rt->ptr_handles = nullptr;
  }
  if (rt->workerThreadPool) {
    delete rt->workerThreadPool;
    rt->workerThreadPool = nullptr;
  }
  if (rt->global_handles_) {
    delete rt->global_handles_;
    rt->global_handles_ = nullptr;
  }
  if (rt->qjsvaluevalue_allocator) {
    delete rt->qjsvaluevalue_allocator;
    rt->qjsvaluevalue_allocator = nullptr;
  }

  {
    JSMallocState ms = rt->malloc_state;
    destroy_allocate_instance(&ms.allocate_state);
    if (rt->gc) {
      delete rt->gc;
      rt->gc = nullptr;
    }
    system_free(rt);
  }
}

void JS_FreeRuntimeForEffect(LEPUSRuntime *rt) {
  pthread_mutex_lock(&runtime_mutex);
  std::unordered_set<LEPUSRuntime *> *g_rt_set = js_get_rt_set();
  if (g_rt_set->find(rt) != g_rt_set->end()) g_rt_set->erase(rt);
  pthread_mutex_unlock(&runtime_mutex);
  /* free the classes */
  rt->class_count = 0;
  rt->class_array = NULL;

  /* free the atoms */
  rt->atom_size = 0;
  rt->atom_array = NULL;
  rt->atom_hash = NULL;
  rt->shape_hash = NULL;

  // free trace_gc data
  if (unlikely(rt->workerThreadPool)) {
    delete rt->workerThreadPool;
    rt->workerThreadPool = nullptr;
  }
  if (rt->global_handles_) {
    delete rt->global_handles_;
    rt->global_handles_ = nullptr;
  }

  {
    JSMallocState ms = rt->malloc_state;
    destroy_allocate_instance(&ms.allocate_state);
    if (rt->gc) {
      delete rt->gc;
      rt->gc = nullptr;
    }
  }
}

#if defined(EMSCRIPTEN)
#if defined(ANDROID) || defined(__ANDROID__) || defined(APPLE) || \
    defined(__APPLE__) || defined(QJS_UNITTEST) || defined(_WIN32)

static inline uint8_t *js_get_stack_pointer(void) {
  return static_cast<uint8_t *>(__builtin_frame_address(0));
}

#else
/* currently no stack limitation */
static inline uint8_t *js_get_stack_pointer(void) { return NULL; }
#endif
#else
/* Note: OS and CPU dependent */
static inline uint8_t *js_get_stack_pointer(void) {
  return __builtin_frame_address(0);
}
#endif

// <primjs begin>

void PrimInit_GC(LEPUSContext *ctx);
// <primjs end>
LEPUSContext *JS_NewContextRaw_GC(LEPUSRuntime *rt) {
#if defined(ENABLE_PRIMJS_TRACE) && PRINT_LOG_TO_FILE && \
    (defined(ANDROID) || defined(__ANDROID__))
  pthread_mutex_lock(&prim_init_mutex);
  if (!log_f) {
    chdir("/data/local/tmp/");
    log_f = fopen("log.txt", "w");
    if (!log_f) {
      LOGE("open fail errno = %d reason = %s \n", errno, strerror(errno));
      abort();
    }
    LOGI("\nlogging to /data/local/tmp/log.txt! ---------\n\n");
  }
  pthread_mutex_unlock(&prim_init_mutex);
#endif
  LEPUSContext *ctx;
  int i;

  ctx = static_cast<LEPUSContext *>(system_mallocz(sizeof(LEPUSContext)));
  if (!ctx) return NULL;
  ctx->gc_enable = rt->gc_enable;
  ctx->ptr_handles = rt->ptr_handles;
  ctx->class_proto = static_cast<LEPUSValue *>(
      lepus_malloc_rt(rt, sizeof(ctx->class_proto[0]) * rt->class_count,
                      ALLOC_TAG_WITHOUT_PTR));
  if (!ctx->class_proto) {
    return NULL;
  }
  ctx->rt = rt;

  list_add_tail(&ctx->link, &rt->context_list);

  for (i = 0; i < rt->class_count; i++) ctx->class_proto[i] = LEPUS_NULL;
  ctx->regexp_ctor = LEPUS_NULL;
  ctx->promise_ctor = LEPUS_NULL;
  ctx->no_lepus_strict_mode = false;
  init_list_head(&ctx->loaded_modules);
  JS_AddIntrinsicBasicObjects_GC(ctx);
  // <Primjs begin>
  ctx->next_function_id = 1;   // for lepusNG sourcemap, need to start from 1
  ctx->debuginfo_outside = 2;  // 2: uninitialize, 1: true, 0: false
  ctx->lynx_target_sdk_version = nullptr;
  ctx->is_lepusng = rt->is_lepusng;
#ifdef QJS_UNITTEST
  ctx->debugger_need_polling = true;
#else
  if (rt->rt_info && rt->is_lepusng) {
    ctx->debugger_need_polling = true;
  }
#endif
  ctx->fg_ctx = static_cast<FinalizationRegistryContext *>(lepus_malloc(
      ctx, sizeof(FinalizationRegistryContext), ALLOC_TAG_WITHOUT_PTR));
  ctx->fg_ctx->ctx = ctx;

  PRIM_LOG("Use snapshot!\n");
  pthread_mutex_lock(&prim_init_mutex);
  PrimInit_GC(ctx);
  pthread_mutex_unlock(&prim_init_mutex);
  // <primjs end>
  return ctx;
}

static void JS_AddIntrinsicWeakRef(LEPUSContext *ctx);
static void JS_AddIntrinsicFinalizationRegistry(LEPUSContext *ctx);
static void JS_AddIntrinsicBigInt(LEPUSContext *ctx);

LEPUSContext *JS_NewContext_GC(LEPUSRuntime *rt) {
  LEPUSContext *ctx;

  ctx = JS_NewContextRaw_GC(rt);
  if (!ctx) return NULL;

  JS_AddIntrinsicBaseObjects_GC(ctx);
  JS_AddIntrinsicDate_GC(ctx);
  JS_AddIntrinsicEval_GC(ctx);
  JS_AddIntrinsicStringNormalize_GC(ctx);
  JS_AddIntrinsicRegExp_GC(ctx);
  JS_AddIntrinsicJSON_GC(ctx);
  JS_AddIntrinsicProxy_GC(ctx);
  JS_AddIntrinsicMapSet_GC(ctx);
  JS_AddIntrinsicTypedArrays_GC(ctx);
  JS_AddIntrinsicPromise_GC(ctx);
  JS_AddIntrinsicWeakRef(ctx);
  JS_AddIntrinsicFinalizationRegistry(ctx);
  JS_AddIntrinsicBigInt(ctx);
  return ctx;
}

/* set the new value and free the old value after (freeing the value
   can reallocate the object data) */
void set_value_gc(LEPUSContext *ctx, LEPUSValue *pval, LEPUSValue new_val) {
  *pval = new_val;
}

void JS_SetClassProto_GC(LEPUSContext *ctx, LEPUSClassID class_id,
                         LEPUSValue obj) {
  LEPUSRuntime *rt = ctx->rt;
  assert(class_id < rt->class_count);
  set_value_gc(ctx, &ctx->class_proto[class_id], obj);
}

LEPUSValue JS_GetClassProto_GC(LEPUSContext *ctx, LEPUSClassID class_id) {
  LEPUSRuntime *rt = ctx->rt;
  assert(class_id < rt->class_count);
  return ctx->class_proto[class_id];
}

void JS_FreeContext_GC(LEPUSContext *ctx) {
  js_free_shape_null(ctx->rt, ctx->array_shape);
  list_del(&ctx->link);
  ctx->fg_ctx->ctx = nullptr;
  if (ctx->napi_scope) {
    delete ctx->napi_scope;
    ctx->napi_scope = nullptr;
  }
  if (ctx->object_ctx_check && ctx->check_tools) {
    delete ctx->check_tools;
  }
  system_free(ctx);
}

QJS_STATIC inline BOOL is_strict_mode(LEPUSContext *ctx) {
  LEPUSStackFrame *sf = ctx->rt->current_stack_frame;
  return (sf && (sf->js_mode & JS_MODE_STRICT));
}

LEPUSValue JS_NewInt64_GC(LEPUSContext *ctx, int64_t v) {
  if (v == (int32_t)v) {
    return LEPUS_NewInt32(ctx, v);
  } else {
    return __JS_NewFloat64(ctx, static_cast<double>(v));
  }
}

QJS_STATIC force_inline LEPUSValue JS_NewUint32(LEPUSContext *ctx,
                                                uint32_t val) {
  LEPUSValue v;
  if (val <= 0x7fffffff) {
    v = LEPUS_MKVAL(LEPUS_TAG_INT, static_cast<int32_t>(val));
  } else {
    v = __JS_NewFloat64(ctx, val);
  }
  return v;
}

/* JSAtom support */

QJS_STATIC inline int is_num(int c) { return c >= '0' && c <= '9'; }

#ifdef ENABLE_LEPUSNG
static inline int32_t JS_GetIdxFromProp(JSAtom prop) {
  int idx = -1;
  if (__JS_AtomIsTaggedInt(prop)) {
    idx = __JS_AtomToUInt32(prop);
  }
  return idx;
}

// if prop is not found, the callback function returns LEPUS_UNINITIALIZED.
static inline LEPUSValue JSRefGetOwnProperty(LEPUSContext *ctx, LEPUSValue obj,
                                             JSAtom prop) {
  return ctx->rt->js_callbacks_.get_property(ctx, obj, prop,
                                             JS_GetIdxFromProp(prop));
}

static inline LEPUSValue JSRefGetProperty(LEPUSContext *ctx, LEPUSValue obj,
                                          JSAtom prop, LEPUSValueConst this_obj,
                                          BOOL throw_ref_error) {
  assert(LEPUS_IsLepusRef(obj));
  LEPUSValue ret = JSRefGetOwnProperty(ctx, obj, prop);
  if (!LEPUS_IsUninitialized(ret)) {
    return ret;
  }

  // find the property in prototype
  if (JS_LepusRefIsArray(ctx->rt, obj)) {
    return JS_GetPropertyInternal_GC(ctx, ctx->class_proto[JS_CLASS_ARRAY],
                                     prop, this_obj, throw_ref_error);
  } else if (JS_LepusRefIsTable(ctx->rt, obj)) {
    return JS_GetPropertyInternal_GC(ctx, ctx->class_proto[JS_CLASS_OBJECT],
                                     prop, this_obj, throw_ref_error);
  }
  return LEPUS_UNDEFINED;
}

static inline int32_t JSRefHasOwnProperty(LEPUSContext *ctx, LEPUSValue obj,
                                          JSAtom prop) {
  assert(LEPUS_IsLepusRef(obj));
  return ctx->rt->primjs_callbacks_.js_has_property(ctx, obj, prop,
                                                    JS_GetIdxFromProp(prop));
}

static inline int32_t JSRefHasProperty(LEPUSContext *ctx, LEPUSValue obj,
                                       JSAtom prop) {
  assert(LEPUS_IsLepusRef(obj));
  if (JSRefHasOwnProperty(ctx, obj, prop)) return 1;
  // if it's not found, find prop in the prototype
  if (JS_LepusRefIsArray(ctx->rt, obj)) {
    LEPUSValue array_proto = ctx->class_proto[JS_CLASS_ARRAY];
    return JS_HasProperty_GC(ctx, array_proto, prop);
  } else if (JS_LepusRefIsTable(ctx->rt, obj)) {
    LEPUSValue obj_proto = ctx->class_proto[JS_CLASS_OBJECT];
    return JS_HasProperty_GC(ctx, obj_proto, prop);
  }
  return 0;
}

#endif

/* XXX: could use faster version ? */
QJS_STATIC inline uint32_t hash_string8(const uint8_t *str, size_t len,
                                        uint32_t h) {
  size_t i;

  for (i = 0; i < len; i++) h = h * 263 + str[i];
  return h;
}

QJS_STATIC inline uint32_t hash_string16(const uint16_t *str, size_t len,
                                         uint32_t h) {
  size_t i;

  for (i = 0; i < len; i++) h = h * 263 + str[i];
  return h;
}

static uint32_t hash_string(const JSString *str, uint32_t h) {
  if (str->is_wide_char)
    h = hash_string16(str->u.str16, str->len, h);
  else
    h = hash_string8(str->u.str8, str->len, h);
  return h;
}

// <Primjs begin>
#if defined(DUMP_QJS_VALUE)
static __attribute__((unused)) void JS_DumpStringNoPrint(LEPUSRuntime *rt,
                                                         const JSString *p,
                                                         char dump_buf[]) {
  int i, c, sep;
  char fmt_buf[1024];

#define write(...) \
  snprintf(fmt_buf, sizeof(fmt_buf), __VA_ARGS__), strcat(dump_buf, fmt_buf);
  if (p == NULL) {
    write("<null>");
    return;
  }
  // write("%d", p->header.ref_count);
  sep = '\'';
  write("%c", sep);
  for (i = 0; i < p->len; i++) {
    if (p->is_wide_char)
      c = p->u.str16[i];
    else
      c = p->u.str8[i];
    if (c == sep || c == '\\') {
      write("%c", '\\');
      write("%c", c);
    } else if (c >= ' ' && c <= 126) {
      write("%c", c);
    } else if (c == '\n') {
      write("%c", '\\');
      write("%c", 'n');
    } else {
      write("\\u%04x", c);
    }
  }
  write("%c", sep);

#undef write
}

static __attribute__((unused)) void JS_DumpString(LEPUSRuntime *rt,
                                                  const JSString *p) {
  char dump_buf[4096];
  dump_buf[0] = '\0';
  JS_DumpStringNoPrint(rt, p, dump_buf);
  printf("%s", dump_buf);
}

static __attribute__((unused)) void JS_DumpAtoms(LEPUSRuntime *rt) {
  JSAtomStruct *p;
  int h, i;
  /* This only dumps hashed atoms, not JS_ATOM_TYPE_SYMBOL atoms */
  printf("JSAtom count=%d size=%d hash_size=%d:\n", rt->atom_count,
         rt->atom_size, rt->atom_hash_size);
  printf("JSAtom hash table: {\n");
  for (i = 0; i < rt->atom_hash_size; i++) {
    h = rt->atom_hash[i];
    if (h) {
      printf("  %d:", i);
      while (h) {
        p = rt->atom_array[h];
        printf(" ");
        JS_DumpString(rt, p);
        h = p->hash_next;
      }
      printf("\n");
    }
  }
  printf("}\n");
  printf("JSAtom table: {\n");
  for (i = 0; i < rt->atom_size; i++) {
    p = rt->atom_array[i];
    if (!atom_is_free(p)) {
      printf("  %d: { %d %08x ", i, p->atom_type, p->hash);
      if (!(p->len == 0 && p->is_wide_char != 0)) JS_DumpString(rt, p);
      printf(" %d }\n", p->hash_next);
    }
  }
  printf("}\n");
}
#endif  // DUMP_QJS_VALUE
// <Primjs end>

JSAtom JS_NewAtomUInt32_GC(LEPUSContext *ctx, uint32_t n) {
  if (n <= JS_ATOM_MAX_INT) {
    return __JS_AtomFromUInt32(n);
  } else {
    char buf[11];
    LEPUSValue val;
    snprintf(buf, sizeof(buf), "%u", n);
    val = JS_NewString_GC(ctx, buf);
    if (LEPUS_IsException(val)) return JS_ATOM_NULL;
    HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
    return __JS_NewAtom(ctx->rt, LEPUS_VALUE_GET_STRING(val),
                        JS_ATOM_TYPE_STRING);
  }
}

static JSAtom JS_NewAtomInt64(LEPUSContext *ctx, int64_t n) {
  if ((uint64_t)n <= JS_ATOM_MAX_INT) {
    return __JS_AtomFromUInt32((uint32_t)n);
  } else {
    char buf[24];
    LEPUSValue val;
    snprintf(buf, sizeof(buf), "%" PRId64, n);
    val = JS_NewString_GC(ctx, buf);
    if (LEPUS_IsException(val)) return JS_ATOM_NULL;
    HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
    return __JS_NewAtom(ctx->rt, LEPUS_VALUE_GET_STRING(val),
                        JS_ATOM_TYPE_STRING);
  }
}

/* 'p' is freed */
static LEPUSValue JS_NewSymbol(LEPUSContext *ctx, JSString *p, int atom_type) {
  LEPUSRuntime *rt = ctx->rt;
  JSAtom atom;
  atom = __JS_NewAtom(rt, p, atom_type);
  if (atom == JS_ATOM_NULL) return LEPUS_ThrowOutOfMemory(ctx);
  set_alloc_tag(rt->atom_array[atom], ALLOC_TAG_JSSymbol);
  return LEPUS_MKPTR(LEPUS_TAG_SYMBOL, rt->atom_array[atom]);
}

/* descr must be a non-numeric string atom */
LEPUSValue JS_NewSymbolFromAtom_GC(LEPUSContext *ctx, JSAtom descr,
                                   int atom_type) {
  LEPUSRuntime *rt = ctx->rt;
  JSString *p;

  assert(!__JS_AtomIsTaggedInt(descr));
  assert(descr < rt->atom_size);
  p = rt->atom_array[descr];
  return JS_NewSymbol(ctx, p, atom_type);
}

#define ATOM_GET_STR_BUF_SIZE 64

LEPUSValue __JS_AtomToValue_GC(LEPUSContext *ctx, JSAtom atom,
                               BOOL force_string) {
  char buf[ATOM_GET_STR_BUF_SIZE];

  if (__JS_AtomIsTaggedInt(atom)) {
    snprintf(buf, sizeof(buf), "%u", __JS_AtomToUInt32(atom));
    return JS_NewString_GC(ctx, buf);
  } else {
    LEPUSRuntime *rt = ctx->rt;
    JSAtomStruct *p;
    assert(atom < rt->atom_size);
    p = rt->atom_array[atom];
    if (p->atom_type == JS_ATOM_TYPE_STRING) {
      goto ret_string;
    } else if (force_string) {
      if (p->len == 0 && p->is_wide_char != 0) {
        /* no description string */
        p = rt->atom_array[JS_ATOM_empty_string];
      }
    ret_string:
      return LEPUS_MKPTR(LEPUS_TAG_STRING, p);
    } else {
      set_alloc_tag(p, ALLOC_TAG_JSSymbol);
      return LEPUS_MKPTR(LEPUS_TAG_SYMBOL, p);
    }
  }
}

LEPUSValue JS_AtomToValue_GC(LEPUSContext *ctx, JSAtom atom) {
  return __JS_AtomToValue_GC(ctx, atom, FALSE);
}

LEPUSValue JS_AtomToString_GC(LEPUSContext *ctx, JSAtom atom) {
  return __JS_AtomToValue_GC(ctx, atom, TRUE);
}

/* This test must be fast if atom is not a numeric index (e.g. a
   method name). Return LEPUS_UNDEFINED if not a numeric
   index. LEPUS_EXCEPTION can also be returned. */
static LEPUSValue JS_AtomIsNumericIndex1(LEPUSContext *ctx, JSAtom atom) {
  LEPUSRuntime *rt = ctx->rt;
  JSAtomStruct *p1;
  JSString *p;
  int c, len, ret;
  LEPUSValue num = LEPUS_UNDEFINED, str = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &num, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&str, HANDLE_TYPE_LEPUS_VALUE);

  if (__JS_AtomIsTaggedInt(atom))
    return LEPUS_NewInt32(ctx, __JS_AtomToUInt32(atom));
  assert(atom < rt->atom_size);
  p1 = rt->atom_array[atom];
  if (p1->atom_type != JS_ATOM_TYPE_STRING) return LEPUS_UNDEFINED;
  p = p1;
  len = p->len;
  if (p->is_wide_char) {
    const uint16_t *r = p->u.str16, *r_end = p->u.str16 + len;
    if (r >= r_end) return LEPUS_UNDEFINED;
    c = *r;
    if (c == '-') {
      if (r >= r_end) return LEPUS_UNDEFINED;
      r++;
      c = *r;
      /* -0 case is specific */
      if (c == '0' && len == 2) goto minus_zero;
    }
    /* XXX: should test NaN, but the tests do not check it */
    if (!is_num(c)) {
      /* XXX: String should be normalized, therefore 8-bit only */
      const uint16_t nfinity16[7] = {'n', 'f', 'i', 'n', 'i', 't', 'y'};
      if (!(c == 'I' && (r_end - r) == 8 &&
            !memcmp(r + 1, nfinity16, sizeof(nfinity16))))
        return LEPUS_UNDEFINED;
    }
  } else {
    const uint8_t *r, *r_end;
    r = p->u.str8;
    r_end = p->u.str8 + len;
    if (r >= r_end) return LEPUS_UNDEFINED;
    c = *r;
    if (c == '-') {
      if (r >= r_end) return LEPUS_UNDEFINED;
      r++;
      c = *r;
      /* -0 case is specific */
      if (c == '0' && len == 2) {
      minus_zero:
        return __JS_NewFloat64(ctx, -0.0);
      }
    }
    if (!is_num(c)) {
      if (!(c == 'I' && (r_end - r) == 8 && !memcmp(r + 1, "nfinity", 7)))
        return LEPUS_UNDEFINED;
    }
  }
  /* XXX: bignum: would be better to only accept integer to avoid
     relying on current floating point precision */
  /* this is ECMA CanonicalNumericIndexString primitive */
  num = JS_ToNumber(ctx, LEPUS_MKPTR(LEPUS_TAG_STRING, p));
  if (LEPUS_IsException(num)) return num;
  str = JS_ToString_GC(ctx, num);
  if (LEPUS_IsException(str)) {
    return str;
  }
  ret = js_string_compare(ctx, p, LEPUS_VALUE_GET_STRING(str));
  if (ret == 0) {
    return num;
  } else {
    return LEPUS_UNDEFINED;
  }
}

/* return -1 if exception or TRUE/FALSE */
static int JS_AtomIsNumericIndex(LEPUSContext *ctx, JSAtom atom) {
  LEPUSValue num;
  num = JS_AtomIsNumericIndex1(ctx, atom);
  if (likely(LEPUS_IsUndefined(num))) return FALSE;
  if (LEPUS_IsException(num)) return -1;
  return TRUE;
}

/* return TRUE if 'v' is a symbol with a string description */
static BOOL JS_AtomSymbolHasDescription(LEPUSContext *ctx, JSAtom v) {
  LEPUSRuntime *rt;
  JSAtomStruct *p;

  rt = ctx->rt;
  if (__JS_AtomIsTaggedInt(v)) return FALSE;
  p = rt->atom_array[v];
  return (((p->atom_type == JS_ATOM_TYPE_SYMBOL &&
            p->hash == JS_ATOM_HASH_SYMBOL) ||
           p->atom_type == JS_ATOM_TYPE_GLOBAL_SYMBOL) &&
          !(p->len == 0 && p->is_wide_char != 0));
}

/* free with LEPUS_FreeCString() */
const char *JS_AtomToCString_GC(LEPUSContext *ctx, JSAtom atom) {
  LEPUSValue str;
  const char *cstr;

  str = JS_AtomToString_GC(ctx, atom);
  if (LEPUS_IsException(str)) return NULL;
  HandleScope func_scope(ctx, &str, HANDLE_TYPE_LEPUS_VALUE);
  cstr = JS_ToCStringLen2_GC(ctx, NULL, str, 0);
  return cstr;
}

QJS_STATIC inline BOOL JS_IsEmptyString(LEPUSValueConst v) {
  return (LEPUS_VALUE_IS_STRING(v) && LEPUS_VALUE_GET_STRING(v)->len == 0) ||
         (JS_IsSeparableString(v) && JS_GetSeparableString(v)->len == 0);
}

static LEPUSValue js_new_string16(LEPUSContext *ctx, const uint16_t *buf,
                                  int len) {
  JSString *str;
  str = js_alloc_string(ctx, len, 1);
  if (!str) return LEPUS_EXCEPTION;
  memcpy(str->u.str16, buf, len * 2);
  return LEPUS_MKPTR(LEPUS_TAG_STRING, str);
}

static LEPUSValue js_new_string_char(LEPUSContext *ctx, uint16_t c) {
  if (c < 0x100) {
    uint8_t ch8 = c;
    return js_new_string8(ctx, &ch8, 1);
  } else {
    uint16_t ch16 = c;
    return js_new_string16(ctx, &ch16, 1);
  }
}

static LEPUSValue js_sub_string(LEPUSContext *ctx, JSString *p, int start,
                                int end) {
  int len = end - start;
  if (start == 0 && end == p->len) {
    return LEPUS_MKPTR(LEPUS_TAG_STRING, p);
  }
  if (p->is_wide_char && len > 0) {
    JSString *str;
    int i;
    uint16_t c = 0;
    for (i = start; i < end; i++) {
      c |= p->u.str16[i];
    }
    if (c > 0xFF) return js_new_string16(ctx, p->u.str16 + start, len);

    str = js_alloc_string(ctx, len, 0);
    if (!str) return LEPUS_EXCEPTION;
    for (i = 0; i < len; i++) {
      str->u.str8[i] = p->u.str16[start + i];
    }
    str->u.str8[len] = '\0';
    return LEPUS_MKPTR(LEPUS_TAG_STRING, str);
  } else {
    return js_new_string8(ctx, p->u.str8 + start, len);
  }
}

/* It is valid to call string_buffer_end() and all string_buffer functions even
   if string_buffer_init() or another string_buffer function returns an error.
   If the error_status is set, string_buffer_end() returns LEPUS_EXCEPTION.
 */
static int string_buffer_init2(LEPUSContext *ctx, StringBuffer *s, int size,
                               int is_wide) {
  s->ctx = ctx;
  s->size = size;
  s->len = 0;
  s->is_wide_char = is_wide;
  s->error_status = 0;
  s->str = js_alloc_string(ctx, size, is_wide);
  if (unlikely(!s->str)) {
    s->size = 0;
    return s->error_status = -1;
  }
#ifdef DUMP_LEAKS
  /* the StringBuffer may reallocate the JSString, only link it at the end */
  list_del(&s->str->link);
#endif
  return 0;
}

static inline int string_buffer_init(LEPUSContext *ctx, StringBuffer *s,
                                     int size) {
  return string_buffer_init2(ctx, s, size, 0);
}

static int string_buffer_set_error(StringBuffer *s) {
  s->str = NULL;
  s->size = 0;
  s->len = 0;
  return s->error_status = -1;
}

static int string_get(const JSString *p, int idx) {
  return p->is_wide_char ? p->u.str16[idx] : p->u.str8[idx];
}

/* appending an ASCII string */
static int string_buffer_puts8(StringBuffer *s, const char *str) {
  return string_buffer_write8(s, (const uint8_t *)str, strlen(str));
}

static int string_buffer_concat_value(StringBuffer *s, LEPUSValueConst v) {
  JSString *p;
  LEPUSValue v1;
  int res;

  if (s->error_status) {
    /* prevent exception overload */
    return -1;
  }
  if (unlikely(!LEPUS_VALUE_IS_STRING(v))) {
    v1 = JS_ToString_GC(s->ctx, v);
    if (LEPUS_IsException(v1)) return string_buffer_set_error(s);
    HandleScope func_scope(s->ctx, &v1, HANDLE_TYPE_LEPUS_VALUE);
    p = LEPUS_VALUE_GET_STRING(v1);
    res = string_buffer_concat(s, p, 0, p->len);
    return res;
  }
  p = LEPUS_VALUE_GET_STRING(v);
  return string_buffer_concat(s, p, 0, p->len);
}

static int string_buffer_concat_value_free(StringBuffer *s, LEPUSValue v) {
  JSString *p;
  int res;

  if (s->error_status) {
    /* prevent exception overload */
    return -1;
  }
  if (unlikely(!LEPUS_VALUE_IS_STRING(v))) {
    v = JS_ToStringFree(s->ctx, v);
    if (LEPUS_IsException(v)) return string_buffer_set_error(s);
  }
  p = LEPUS_VALUE_GET_STRING(v);
  HandleScope func_scope(s->ctx, p, HANDLE_TYPE_DIR_HEAP_OBJ);
  res = string_buffer_concat(s, p, 0, p->len);
  return res;
}

static LEPUSValue string_buffer_end(StringBuffer *s) {
  JSString *str;
  str = s->str;
  if (s->error_status) return LEPUS_EXCEPTION;
  if (s->len == 0) {
    s->str = NULL;
    return JS_AtomToString_GC(s->ctx, JS_ATOM_empty_string);
  }
  if (s->len < s->size) {
    /* smaller size so lepus_realloc should not fail, but OK if it does */
    /* XXX: should add some slack to avoid unnecessary calls */
    /* XXX: might need to use malloc+free to ensure smaller size */
    str = static_cast<JSString *>(lepus_realloc_rt(
        s->ctx->rt, str,
        sizeof(JSString) + (s->len << s->is_wide_char) + 1 - s->is_wide_char,
        ALLOC_TAG_JSString));
    if (str == NULL) str = s->str;
    s->str = str;
  }
  if (!s->is_wide_char) str->u.str8[s->len] = 0;
#ifdef DUMP_LEAKS
  list_add_tail(&str->link, &s->ctx->rt->string_list);
#endif
  str->is_wide_char = s->is_wide_char;
  str->len = s->len;
  s->str = NULL;
  return LEPUS_MKPTR(LEPUS_TAG_STRING, str);
}

/* create a string from a UTF-8 buffer */
LEPUSValue JS_NewStringLen_GC(LEPUSContext *ctx, const char *buf,
                              size_t buf_len) {
  const uint8_t *p, *p_end, *p_start, *p_next;
  uint32_t c;
  size_t len1;

  p_start = (const uint8_t *)buf;
  p_end = p_start + buf_len;
  p = p_start;
  while (p < p_end && *p < 128) p++;
  len1 = p - p_start;
  if (len1 > JS_STRING_LEN_MAX)
    return LEPUS_ThrowInternalError(ctx, "string too long");
  if (p == p_end) {
    /* ASCII string */
    return js_new_string8(ctx, (const uint8_t *)buf, buf_len);
  } else {
    HandleScope block_scope(ctx->rt);
    StringBuffer b_s, *b = &b_s;
    if (string_buffer_init(ctx, b, buf_len)) {
      b->str = NULL;
      return LEPUS_EXCEPTION;
    }
    block_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);
    string_buffer_write8(b, p_start, len1);
    while (p < p_end) {
      if (*p < 128) {
        string_buffer_putc8(b, *p++);
      } else {
        /* parse utf-8 sequence, return 0xFFFFFFFF for error */
        c = unicode_from_utf8(p, p_end - p, &p_next);
        if (c < 0x10000) {
          p = p_next;
        } else if (c <= 0x10FFFF) {
          p = p_next;
          /* surrogate pair */
          c -= 0x10000;
          string_buffer_putc16(b, (c >> 10) + 0xd800);
          c = (c & 0x3ff) + 0xdc00;
        } else {
          /* invalid char */
          c = 0xfffd;
          /* skip the invalid chars */
          /* XXX: seems incorrect. Why not just use c = *p++; ? */
          while (p < p_end && (*p >= 0x80 && *p < 0xc0)) p++;
          if (p < p_end) {
            p++;
            while (p < p_end && (*p >= 0x80 && *p < 0xc0)) p++;
          }
        }
        string_buffer_putc16(b, c);
      }
    }
    return string_buffer_end(b);
  }
}

static LEPUSValue JS_ConcatString3(LEPUSContext *ctx, const char *str1,
                                   LEPUSValue str2, const char *str3) {
  HandleScope func_scope(ctx);
  StringBuffer b_s, *b = &b_s;
  int len1, len3;
  JSString *p;

  if (unlikely(!LEPUS_VALUE_IS_STRING(str2))) {
    str2 = JS_ToStringFree(ctx, str2);
    if (LEPUS_IsException(str2)) goto fail;
    func_scope.PushHandle(&str2, HANDLE_TYPE_LEPUS_VALUE);
  }
  p = LEPUS_VALUE_GET_STRING(str2);
  len1 = strlen(str1);
  len3 = strlen(str3);

  if (string_buffer_init2(ctx, b, len1 + p->len + len3, p->is_wide_char))
    goto fail;
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);
  string_buffer_write8(b, (const uint8_t *)str1, len1);
  string_buffer_concat(b, p, 0, p->len);
  string_buffer_write8(b, (const uint8_t *)str3, len3);

  return string_buffer_end(b);

fail:
  return LEPUS_EXCEPTION;
}

LEPUSValue JS_NewString_GC(LEPUSContext *ctx, const char *str) {
  return JS_NewStringLen_GC(ctx, str, strlen(str));
}

LEPUSValue JS_NewAtomString_GC(LEPUSContext *ctx, const char *str) {
  JSAtom atom = LEPUS_NewAtom(ctx, str);
  if (atom == JS_ATOM_NULL) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx->rt);
  func_scope.PushLEPUSAtom(atom);
  LEPUSValue val = JS_AtomToString_GC(ctx, atom);
  return val;
}

/* return (NULL, 0) if exception. */
/* return pointer into a JSString with a live ref_count */
/* cesu8 determines if non-BMP1 codepoints are encoded as 1 or 2 utf-8 sequences
 */
const char *JS_ToCStringLen2_GC(LEPUSContext *ctx, size_t *plen,
                                LEPUSValueConst val1, BOOL cesu8) {
  HandleScope func_scope(ctx);
  LEPUSValue val;
  JSString *str, *str_new;
  int pos, len, c, c1;
  uint8_t *q;

  if (!LEPUS_VALUE_IS_STRING(val1)) {
    val = JS_ToString_GC(ctx, val1);
    if (LEPUS_IsException(val)) goto fail;
    func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  } else {
    val = val1;
  }

  str = LEPUS_VALUE_GET_STRING(val);
  len = str->len;
  if (!str->is_wide_char) {
    const uint8_t *src = str->u.str8;
    int count;

    /* count the number of non-ASCII characters */
    /* Scanning the whole string is required for ASCII strings,
       and computing the number of non-ASCII bytes is less expensive
       than testing each byte, hence this method is faster for ASCII
       strings, which is the most common case.
     */
    count = 0;
    for (pos = 0; pos < len; pos++) {
      count += src[pos] >> 7;
    }
    if (count == 0) {
      if (plen) *plen = len;
      return (const char *)src;
    }
    str_new = js_alloc_string(ctx, len + count, 0);
    if (!str_new) goto fail;
    q = str_new->u.str8;
    for (pos = 0; pos < len; pos++) {
      c = src[pos];
      if (c < 0x80) {
        *q++ = c;
      } else {
        *q++ = (c >> 6) | 0xc0;
        *q++ = (c & 0x3f) | 0x80;
      }
    }
  } else {
    const uint16_t *src = str->u.str16;
    /* Allocate 3 bytes per 16 bit code point. Surrogate pairs may
       produce 4 bytes but use 2 code points.
     */
    str_new = js_alloc_string(ctx, len * 3, 0);
    if (!str_new) goto fail;
    q = str_new->u.str8;
    pos = 0;
    while (pos < len) {
      c = src[pos++];
      if (c < 0x80) {
        *q++ = c;
      } else {
        if (c >= 0xd800 && c < 0xdc00) {
          if (pos < len && !cesu8) {
            c1 = src[pos];
            if (c1 >= 0xdc00 && c1 < 0xe000) {
              pos++;
              /* surrogate pair */
              c = (((c & 0x3ff) << 10) | (c1 & 0x3ff)) + 0x10000;
            } else {
              /* Keep unmatched surrogate code points */
              /* c = 0xfffd; */ /* error */
            }
          } else {
            /* Keep unmatched surrogate code points */
            /* c = 0xfffd; */ /* error */
          }
        }
        q += unicode_to_utf8(q, c);
      }
    }
  }

  *q = '\0';
  str_new->len = q - str_new->u.str8;
  if (plen) *plen = str_new->len;
  return (const char *)str_new->u.str8;
fail:
  if (plen) *plen = 0;
  return NULL;
}

static void copy_str16(uint16_t *dst, const JSString *p, int offset, int len) {
  if (p->is_wide_char) {
    memcpy(dst, p->u.str16 + offset, len * 2);
  } else {
    const uint8_t *src1 = p->u.str8 + offset;
    int i;

    for (i = 0; i < len; i++) dst[i] = src1[i];
  }
}

static LEPUSValue JS_ConcatString1(LEPUSContext *ctx, const JSString *p1,
                                   const JSString *p2) {
  JSString *p;
  uint32_t len;
  int is_wide_char;

  len = p1->len + p2->len;
  if (len > JS_STRING_LEN_MAX)
    return LEPUS_ThrowInternalError(ctx, "string too long");
  is_wide_char = p1->is_wide_char | p2->is_wide_char;
  p = js_alloc_string(ctx, len, is_wide_char);
  if (!p) return LEPUS_EXCEPTION;
  if (!is_wide_char) {
    memcpy(p->u.str8, p1->u.str8, p1->len);
    memcpy(p->u.str8 + p1->len, p2->u.str8, p2->len);
    p->u.str8[len] = '\0';
  } else {
    copy_str16(p->u.str16, p1, 0, p1->len);
    copy_str16(p->u.str16 + p1->len, p2, 0, p2->len);
  }
  return LEPUS_MKPTR(LEPUS_TAG_STRING, p);
}

/* op1 and op2 are converted to strings. For convience, op1 or op2 =
   LEPUS_EXCEPTION are accepted and return LEPUS_EXCEPTION.  */
static LEPUSValue JS_ConcatStringOriginal(LEPUSContext *ctx, LEPUSValue op1,
                                          LEPUSValue op2) {
  LEPUSValue ret;
  JSString *p1, *p2;
  HandleScope func_scope(ctx);

  if (unlikely(!LEPUS_VALUE_IS_STRING(op1))) {
    op1 = JS_ToStringFree(ctx, op1);
    if (LEPUS_IsException(op1)) {
      return LEPUS_EXCEPTION;
    }
    func_scope.PushHandle(&op1, HANDLE_TYPE_LEPUS_VALUE);
  }
  if (unlikely(!LEPUS_VALUE_IS_STRING(op2))) {
    op2 = JS_ToStringFree(ctx, op2);
    if (LEPUS_IsException(op2)) {
      return LEPUS_EXCEPTION;
    }
    func_scope.PushHandle(&op2, HANDLE_TYPE_LEPUS_VALUE);
  }
  p1 = LEPUS_VALUE_GET_STRING(op1);
  p2 = LEPUS_VALUE_GET_STRING(op2);

  /* XXX: could also check if p1 is empty */
  if (p2->len == 0) {
    return op1;
  }
  ret = JS_ConcatString1(ctx, p1, p2);
  return ret;
}

static LEPUSValue JS_ConcatSeparableString(LEPUSContext *ctx, LEPUSValue op1,
                                           LEPUSValue op2) {
  LEPUSValue ret;
  JSString *p1, *p2;
  HandleScope func_scope(ctx);

  if (unlikely(!LEPUS_IsString(op1))) {
    op1 = JS_ToStringFree(ctx, op1);
    if (LEPUS_IsException(op1)) {
      return LEPUS_EXCEPTION;
    }
    func_scope.PushHandle(&op1, HANDLE_TYPE_LEPUS_VALUE);
  }

  if (unlikely(!(LEPUS_IsString(op2)))) {
    op2 = JS_ToStringFree(ctx, op2);
    if (LEPUS_IsException(op2)) {
      return LEPUS_EXCEPTION;
    }
    func_scope.PushHandle(&op2, HANDLE_TYPE_LEPUS_VALUE);
  }

  auto *separable_string = static_cast<JSSeparableString *>(lepus_malloc(
      ctx, sizeof(JSSeparableString), ALLOC_TAG_JSSeparableString));
  if (!separable_string) return LEPUS_EXCEPTION;
  uint8_t is_wide_char = 0;
  uint32_t len = 0;
  uint32_t depth = 1;
  if (JS_IsSeparableString(op1)) {
    auto *p = JS_GetSeparableString(op1);
    is_wide_char = p->is_wide_char;
    len = p->len;
    depth = p->depth + 1;
  } else {
    p1 = LEPUS_VALUE_GET_STRING(op1);
    is_wide_char = p1->is_wide_char;
    len = p1->len;
  }

  if (JS_IsSeparableString(op2)) {
    auto *p = JS_GetSeparableString(op2);
    is_wide_char |= p->is_wide_char;
    len += p->len;
    depth = (p->depth >= depth) ? p->depth + 1 : depth;
  } else {
    p2 = LEPUS_VALUE_GET_STRING(op2);
    is_wide_char |= p2->is_wide_char;
    len += p2->len;
  }

  separable_string->len = len;
  separable_string->is_wide_char = is_wide_char;
  separable_string->depth = depth;
  separable_string->left_op = op1;
  separable_string->right_op = op2;
  separable_string->flat_content = LEPUS_UNDEFINED;
  return LEPUS_MKPTR(LEPUS_TAG_SEPARABLE_STRING, separable_string);
}

LEPUSValue JS_ConcatString_GC(LEPUSContext *ctx, LEPUSValue op1,
                              LEPUSValue op2) {
  if (separable_string_disabled(ctx->rt)) {
    return JS_ConcatStringOriginal(ctx, op1, op2);
  }
  return JS_ConcatSeparableString(ctx, op1, op2);
}

LEPUSValue JS_GetSeparableStringContentNotDup_GC(LEPUSContext *ctx,
                                                 LEPUSValue val) {
  assert(JS_IsSeparableString(val));
  auto *separable_string = JS_GetSeparableString(val);

  if (LEPUS_VALUE_IS_STRING(separable_string->flat_content)) {
    return separable_string->flat_content;
  }

  HandleScope func_scope(ctx->rt);
  StringBuffer b_s, *b = &b_s;
  string_buffer_init2(ctx, b, separable_string->len,
                      separable_string->is_wide_char);
  func_scope.PushHandle(&(b->str), HANDLE_TYPE_HEAP_OBJ);

  CStack stack(ctx->rt, separable_string->depth);
  auto *cur = separable_string;
  while (cur || !stack.Empty()) {
    while (cur) {
      if (LEPUS_IsUndefined(cur->flat_content)) {
        stack.Push(cur);
        if (JS_IsSeparableString(cur->left_op)) {
          cur = JS_GetSeparableString(cur->left_op);
          continue;
        } else {
          string_buffer_concat_value(b, cur->left_op);
        }
      } else {
        string_buffer_concat_value(b, cur->flat_content);
      }
      cur = NULL;
    }

    if (!stack.Empty()) {
      cur = stack.Top();
      stack.Pop();
      if (!JS_IsSeparableString(cur->right_op)) {
        string_buffer_concat_value(b, cur->right_op);
        cur = NULL;
      } else {
        cur = JS_GetSeparableString(cur->right_op);
      }
    }
  }
  separable_string->flat_content = string_buffer_end(b);
  separable_string->left_op = LEPUS_NULL;
  separable_string->right_op = LEPUS_NULL;
  return separable_string->flat_content;
}

LEPUSValue JS_GetSeparableStringContent_GC(LEPUSContext *ctx, LEPUSValue val) {
  return JS_GetSeparableStringContentNotDup_GC(ctx, val);
}

/* Shape support */

static int init_shape_hash(LEPUSRuntime *rt) {
  rt->shape_hash_bits = 4; /* 16 shapes */
  rt->shape_hash_size = 1 << rt->shape_hash_bits;
  rt->shape_hash_count = 0;
  rt->shape_hash = static_cast<JSShape **>(
      lepus_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * rt->shape_hash_size,
                       ALLOC_TAG_WITHOUT_PTR));
  if (!rt->shape_hash) return -1;
  return 0;
}

/* same magic hash multiplier as the Linux kernel */
static uint32_t shape_hash(uint32_t h, uint32_t val) {
  return (h + val) * 0x9e370001;
}

/* truncate the shape hash to 'hash_bits' bits */
static uint32_t get_shape_hash(uint32_t h, int hash_bits) {
  return h >> (32 - hash_bits);
}

static uint32_t shape_initial_hash(LEPUSObject *proto) {
  uint32_t h;
  h = shape_hash(1, (uintptr_t)proto);
  if (sizeof(proto) > 4) h = shape_hash(h, (uint64_t)(uintptr_t)proto >> 32);
  return h;
}

static JSShape *js_new_shape_nohash(LEPUSContext *ctx, LEPUSObject *proto,
                                    int hash_size, int prop_size) {
  LEPUSRuntime *rt = ctx->rt;
  void *sh_alloc;
  JSShape *sh;

  sh_alloc = lepus_mallocz(ctx, get_shape_size(hash_size, prop_size),
                           ALLOC_TAG_JSShape);
  if (!sh_alloc) return NULL;
  sh = get_shape_from_alloc(sh_alloc, hash_size);
  sh->header.ref_count = 1;
  sh->proto = proto;
  memset(sh->prop_hash_end - hash_size, 0,
         sizeof(sh->prop_hash_end[0]) * hash_size);
  sh->prop_hash_mask = hash_size - 1;
  sh->prop_size = prop_size;
#ifndef _WIN32
  if (ctx->gc_enable) set_hash_size(sh_alloc, hash_size);
#endif
  return sh;
}

static JSShape *js_new_shape2(LEPUSContext *ctx, LEPUSObject *proto,
                              int32_t hash_size, int32_t prop_size) {
  LEPUSRuntime *rt = ctx->rt;
  JSShape *sh;
  if (2 * (rt->shape_hash_count + 1) > rt->shape_hash_size) {
    resize_shape_hash(rt, rt->shape_hash_bits + 1);
  }

  sh = js_new_shape_nohash(ctx, proto, hash_size, prop_size);

  if (!sh) return nullptr;
  /* insert in the hash table */
  sh->hash = shape_initial_hash(proto);
  sh->is_hashed = TRUE;
  sh->has_small_array_index = FALSE;
  js_shape_hash_link(ctx->rt, sh);
  return sh;
}

static JSShape *js_new_shape(LEPUSContext *ctx, LEPUSObject *proto) {
  return js_new_shape2(ctx, proto, JS_PROP_INITIAL_HASH_SIZE,
                       JS_PROP_INITIAL_SIZE);
}

/* The shape is cloned. The new shape is not inserted in the shape
   hash table */
static JSShape *js_clone_shape(LEPUSContext *ctx, JSShape *sh1) {
  JSShape *sh;
  void *sh_alloc, *sh_alloc1;
  size_t size;
  JSShapeProperty *pr;
  uint32_t i, hash_size;

  hash_size = sh1->prop_hash_mask + 1;
  size = get_shape_size(hash_size, sh1->prop_size);
  sh_alloc = lepus_malloc(ctx, size, ALLOC_TAG_JSShape);
  if (!sh_alloc) return NULL;
  set_hash_size(sh_alloc, hash_size);
  sh_alloc1 = get_alloc_from_shape(sh1);
  memcpy(sh_alloc, sh_alloc1, size);
  sh = get_shape_from_alloc(sh_alloc, hash_size);
  sh->header.ref_count = 1;
  sh->is_hashed = FALSE;
  return sh;
}

static void js_free_shape(LEPUSRuntime *rt, JSShape *sh) {
  sh->header.ref_count--;
}

static void js_free_shape_null(LEPUSRuntime *rt, JSShape *sh) {
  if (sh) js_free_shape(rt, sh);
}

static int add_shape_property(LEPUSContext *ctx, JSShape **psh, LEPUSObject *p,
                              JSAtom atom, int prop_flags) {
  LEPUSRuntime *rt = ctx->rt;
  JSShape *sh = *psh;
  JSShapeProperty *pr, *prop;
  uint32_t hash_mask, new_shape_hash = 0;
  intptr_t h;

  /* update the shape hash */
  if (sh->is_hashed) {
    js_shape_hash_unlink(rt, sh);
    new_shape_hash = shape_hash(shape_hash(sh->hash, atom), prop_flags);
  }

  if (unlikely(sh->prop_count >= sh->prop_size)) {
    if (resize_properties(ctx, psh, p, sh->prop_count + 1)) {
      /* in case of error, reinsert in the hash table.
         sh is still valid if resize_properties() failed */
      if (sh->is_hashed) js_shape_hash_link(rt, sh);
      return -1;
    }
    sh = *psh;
  }
  if (sh->is_hashed) {
    sh->hash = new_shape_hash;
    js_shape_hash_link(rt, sh);
  }
  /* Initialize the new shape property.
     The object property at p->prop[sh->prop_count] is uninitialized */
  prop = get_shape_prop(sh);
  pr = &prop[sh->prop_count++];
  pr->atom = atom;
  pr->flags = prop_flags;
  sh->has_small_array_index |= __JS_AtomIsTaggedInt(atom);
  /* add in hash table */
  hash_mask = sh->prop_hash_mask;
  h = atom & hash_mask;
  pr->hash_next = sh->prop_hash_end[-h - 1];
  sh->prop_hash_end[-h - 1] = sh->prop_count;
  return 0;
}

/* find a hashed empty shape matching the prototype. Return NULL if
   not found */
static JSShape *find_hashed_shape_proto(LEPUSRuntime *rt, LEPUSObject *proto) {
  JSShape *sh1;
  uint32_t h, h1;

  h = shape_initial_hash(proto);
  h1 = get_shape_hash(h, rt->shape_hash_bits);
  for (sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) {
    if (sh1->hash == h && sh1->proto == proto && sh1->prop_count == 0) {
      return sh1;
    }
  }
  return NULL;
}

/* find a hashed shape matching sh + (prop, prop_flags). Return NULL if
   not found */
static JSShape *find_hashed_shape_prop(LEPUSRuntime *rt, JSShape *sh,
                                       JSAtom atom, int prop_flags) {
  JSShape *sh1;
  uint32_t h, h1, i, n;

  h = sh->hash;
  h = shape_hash(h, atom);
  h = shape_hash(h, prop_flags);
  h1 = get_shape_hash(h, rt->shape_hash_bits);
  for (sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) {
    /* we test the hash first so that the rest is done only if the
       shapes really match */
    if (sh1->hash == h && sh1->proto == sh->proto &&
        sh1->prop_count == ((n = sh->prop_count) + 1)) {
      for (i = 0; i < n; i++) {
        if (unlikely(sh1->prop[i].atom != sh->prop[i].atom) ||
            unlikely(sh1->prop[i].flags != sh->prop[i].flags))
          goto next;
      }
      if (unlikely(sh1->prop[n].atom != atom) ||
          unlikely(sh1->prop[n].flags != prop_flags))
        goto next;
      return sh1;
    }
  next: {}
  }
  return NULL;
}

QJS_HIDE LEPUSValue JS_NewObjectFromShape_GC(LEPUSContext *ctx, JSShape *sh,
                                             LEPUSClassID class_id) {
  HandleScope func_scope(ctx, get_alloc_from_shape(sh),
                         HANDLE_TYPE_DIR_HEAP_OBJ);
  LEPUSObject *p;

  // js_trigger_gc(ctx->rt, sizeof(LEPUSObject));
  p = static_cast<LEPUSObject *>(
      lepus_malloc(ctx, sizeof(LEPUSObject), ALLOC_TAG_LEPUSObject));
  if (unlikely(!p)) goto fail;
  func_scope.PushHandle(p, HANDLE_TYPE_DIR_HEAP_OBJ);
  p->class_id = class_id;
  p->extensible = TRUE;
  p->free_mark = 0;
  p->is_exotic = 0;
  p->fast_array = 0;
  p->is_constructor = 0;
  p->is_uncatchable_error = 0;
  p->is_class = 0;
  p->tmp_mark = 0;
  p->first_weak_ref = NULL;
  p->u.opaque = NULL;
  p->shape = sh;
  p->prop = NULL;
  if (unlikely(ctx->object_ctx_check)) {
    p->ctx = ctx;
    p->tid = get_tid();
  } else {
    p->ctx = nullptr;
    p->tid = 0;
  }

  switch (class_id) {
    case JS_CLASS_OBJECT:
      break;
    case JS_CLASS_ARRAY: {
      p->is_exotic = 1;
      p->fast_array = 1;
      p->u.array.u.values = NULL;
      p->u.array.count = 0;
      p->u.array.u1.size = 0;
    } break;
    case JS_CLASS_C_FUNCTION:
      break;
    case JS_CLASS_ARGUMENTS:
    case JS_CLASS_UINT8C_ARRAY ... JS_CLASS_BIG_UINT64_ARRAY:
      p->is_exotic = 1;
      p->fast_array = 1;
      p->u.array.u.ptr = NULL;
      p->u.array.count = 0;
      break;
    case JS_CLASS_DATAVIEW:
      p->u.array.u.ptr = NULL;
      p->u.array.count = 0;
      break;
    case JS_CLASS_NUMBER:
    case JS_CLASS_STRING:
    case JS_CLASS_BOOLEAN:
    case JS_CLASS_SYMBOL:
    case JS_CLASS_DATE:
      p->u.object_data = LEPUS_UNDEFINED;
      goto set_exotic;
    case JS_CLASS_REGEXP:
      p->u.regexp.pattern = NULL;
      p->u.regexp.bytecode = NULL;
      goto set_exotic;
    case JS_CLASS_BYTECODE_FUNCTION:
    case JS_CLASS_GENERATOR_FUNCTION:
    case JS_CLASS_ASYNC_FUNCTION:
    case JS_CLASS_ASYNC_GENERATOR_FUNCTION:
      p->u.func.home_object = NULL;
      goto set_exotic;
    default:
    set_exotic:
      if (ctx->rt->class_array[class_id].exotic) {
        p->is_exotic = 1;
      }
      break;
  }

  p->prop = static_cast<JSProperty *>(lepus_malloc(
      ctx, sizeof(JSProperty) * sh->prop_size, ALLOC_TAG_WITHOUT_PTR));
  if (unlikely(!p->prop)) {
  fail:
    js_free_shape(ctx->rt, sh);
    return LEPUS_EXCEPTION;
  }

  if (class_id == JS_CLASS_ARRAY) {
    JSProperty *pr;
    /* the length property is always the first one */
    if (likely(sh == ctx->array_shape)) {
      pr = &p->prop[0];
    } else {
      /* only used for the first array */
      /* cannot fail */
      pr = add_property_gc(ctx, p, JS_ATOM_length,
                           LEPUS_PROP_WRITABLE | LEPUS_PROP_LENGTH);
    }
    pr->u.value = LEPUS_NewInt32(ctx, 0);
  } else if (class_id == JS_CLASS_C_FUNCTION) {
    p->prop[0].u.value = LEPUS_UNDEFINED;
  }
  return LEPUS_MKPTR(LEPUS_TAG_OBJECT, p);
}

static LEPUSObject *get_proto_obj(LEPUSValueConst proto_val) {
  if (LEPUS_VALUE_IS_NOT_OBJECT(proto_val))
    return NULL;
  else
    return LEPUS_VALUE_GET_OBJ(proto_val);
}

/* WARNING: proto must be an object or LEPUS_NULL */
LEPUSValue __attribute__((always_inline))
JS_NewObjectProtoClass_GC(LEPUSContext *ctx, LEPUSValueConst proto_val,
                          LEPUSClassID class_id) {
  JSShape *sh;

  LEPUSObject *proto = get_proto_obj(proto_val);
  sh = find_hashed_shape_proto(ctx->rt, proto);
  if (likely(sh)) {
    sh = js_dup_shape(sh);
  } else {
    sh = js_new_shape(ctx, proto);
    if (!sh) return LEPUS_EXCEPTION;
  }
  return JS_NewObjectFromShape_GC(ctx, sh, class_id);
}

static LEPUSValue JS_NewObjectProtoClassAlloc(LEPUSContext *ctx,
                                              LEPUSValueConst proto_val,
                                              LEPUSClassID class_id,
                                              int32_t n_alloc_props) {
  JSShape *sh;
  LEPUSObject *proto;
  int32_t hash_size, hash_bits;

  if (n_alloc_props <= JS_PROP_INITIAL_SIZE) {
    n_alloc_props = JS_PROP_INITIAL_SIZE;
    hash_size = JS_PROP_INITIAL_HASH_SIZE;
  } else {
    hash_bits = 32 - clz32(n_alloc_props - 1);  // ceil(log2(radix))
    hash_size = 1 << hash_bits;
  }

  proto = get_proto_obj(proto_val);
  sh = js_new_shape_nohash(ctx, proto, hash_size, n_alloc_props);
  if (!sh) return LEPUS_EXCEPTION;
  return JS_NewObjectFromShape_GC(ctx, sh, class_id);
}

static LEPUSValue JS_NewObjectProtoList(LEPUSContext *ctx,
                                        LEPUSValueConst proto,
                                        const LEPUSCFunctionListEntry *fields,
                                        int32_t nfields) {
  LEPUSValue obj = LEPUS_UNDEFINED;
  obj = JS_NewObjectProtoClassAlloc(ctx, proto, JS_CLASS_OBJECT, nfields);
  if (LEPUS_IsException(obj)) return obj;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  LEPUS_SetPropertyFunctionList(ctx, obj, fields, nfields);
  return obj;
}

static int JS_SetObjectData(LEPUSContext *ctx, LEPUSValueConst obj,
                            LEPUSValue val) {
  LEPUSObject *p;

  if (LEPUS_VALUE_IS_OBJECT(obj)) {
    p = LEPUS_VALUE_GET_OBJ(obj);
    switch (p->class_id) {
      case JS_CLASS_NUMBER:
      case JS_CLASS_STRING:
      case JS_CLASS_BOOLEAN:
      case JS_CLASS_SYMBOL:
      case JS_CLASS_DATE:
      case JS_CLASS_BIG_INT:
        p->u.object_data = val;
        return 0;
    }
  }
  if (!LEPUS_IsException(obj)) LEPUS_ThrowTypeError(ctx, "invalid object type");
  return -1;
}

LEPUSValue JS_NewObjectClass_GC(LEPUSContext *ctx, int class_id) {
  return JS_NewObjectProtoClass_GC(ctx, ctx->class_proto[class_id], class_id);
}

LEPUSValue JS_NewObjectProto_GC(LEPUSContext *ctx, LEPUSValueConst proto) {
  return JS_NewObjectProtoClass_GC(ctx, proto, JS_CLASS_OBJECT);
}

LEPUSValue PRIM_JS_NewArray_GC(LEPUSContext *ctx) {
  return JS_NewObjectFromShape_GC(ctx, js_dup_shape(ctx->array_shape),
                                  JS_CLASS_ARRAY);
}

LEPUSValue JS_NewArray_GC(LEPUSContext *ctx) {
  return JS_NewObjectFromShape_GC(ctx, js_dup_shape(ctx->array_shape),
                                  JS_CLASS_ARRAY);
}

// <primjs begin>
LEPUSValue PRIM_JS_NewObject_GC(LEPUSContext *ctx) {
  /* inline JS_NewObjectClass_GC(ctx, JS_CLASS_OBJECT); */
  return JS_NewObjectProtoClass_GC(ctx, ctx->class_proto[JS_CLASS_OBJECT],
                                   JS_CLASS_OBJECT);
}
// <primjs end>

LEPUSValue JS_NewObject_GC(LEPUSContext *ctx) {
  /* inline JS_NewObjectClass_GC(ctx, JS_CLASS_OBJECT); */
  return JS_NewObjectProtoClass_GC(ctx, ctx->class_proto[JS_CLASS_OBJECT],
                                   JS_CLASS_OBJECT);
}

static void js_function_set_properties(LEPUSContext *ctx, LEPUSObject *p,
                                       JSAtom name, int len) {
  /* ES6 feature non compatible with ES5.1: length is configurable */
  JSProperty *pr;
  static constexpr int32_t prop_flag = LEPUS_PROP_CONFIGURABLE;
  pr = add_property_gc(ctx, p, JS_ATOM_length, prop_flag);
  if (pr) pr->u.value = LEPUS_NewInt32(ctx, len);
  pr = add_property_gc(ctx, p, JS_ATOM_name, prop_flag);
  if (pr) pr->u.value = JS_AtomToString_GC(ctx, name);
  return;
}

void js_method_set_home_object_gc(LEPUSContext *ctx, LEPUSValueConst func_obj,
                                  LEPUSValueConst home_obj) {
  LEPUSObject *p, *p1;
  LEPUSFunctionBytecode *b;

  if (LEPUS_VALUE_IS_NOT_OBJECT(func_obj)) return;
  p = LEPUS_VALUE_GET_OBJ(func_obj);
  if (!lepus_class_has_bytecode(p->class_id)) return;
  b = p->u.func.function_bytecode;
  if (b->need_home_object) {
    p1 = p->u.func.home_object;
    if (LEPUS_VALUE_IS_OBJECT(home_obj))
      p1 = LEPUS_VALUE_GET_OBJ(home_obj);
    else
      p1 = NULL;
    p->u.func.home_object = p1;
  }
}

static LEPUSValue js_get_function_name(LEPUSContext *ctx, JSAtom name) {
  LEPUSValue name_str = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &name_str, HANDLE_TYPE_LEPUS_VALUE);

  name_str = JS_AtomToString_GC(ctx, name);
  if (JS_AtomSymbolHasDescription(ctx, name)) {
    name_str = JS_ConcatString3(ctx, "[", name_str, "]");
  }
  return name_str;
}

/* Modify the name of a method according to the atom and
   'flags'. 'flags' is a bitmask of LEPUS_PROP_HAS_GET and
   LEPUS_PROP_HAS_SET. Also set the home object of the method.
   Return < 0 if exception. */
int js_method_set_properties_gc(LEPUSContext *ctx, LEPUSValueConst func_obj,
                                JSAtom name, int flags,
                                LEPUSValueConst home_obj) {
  LEPUSValue name_str;

  name_str = js_get_function_name(ctx, name);
  HandleScope func_scope(ctx, &name_str, HANDLE_TYPE_LEPUS_VALUE);
  if (flags & LEPUS_PROP_HAS_GET) {
    name_str = JS_ConcatString3(ctx, "get ", name_str, "");
  } else if (flags & LEPUS_PROP_HAS_SET) {
    name_str = JS_ConcatString3(ctx, "set ", name_str, "");
  }
  if (LEPUS_IsException(name_str)) return -1;
  if (JS_DefinePropertyValue_GC(ctx, func_obj, JS_ATOM_name, name_str,
                                LEPUS_PROP_CONFIGURABLE) < 0)
    return -1;
  js_method_set_home_object_gc(ctx, func_obj, home_obj);
  return 0;
}

/* Note: at least 'length' arguments will be readable in 'argv' */
static LEPUSValue JS_NewCFunction3(LEPUSContext *ctx, LEPUSCFunction *func,
                                   const char *name, int length,
                                   LEPUSCFunctionEnum cproto, int magic,
                                   LEPUSValueConst proto_val,
                                   int32_t n_fields = 0) {
  LEPUSValue func_obj;
  LEPUSObject *p;
  JSAtom name_atom;

  if (n_fields > 0) {
    func_obj = JS_NewObjectProtoClassAlloc(ctx, proto_val, JS_CLASS_C_FUNCTION,
                                           n_fields);
  } else {
    func_obj = JS_NewObjectProtoClass_GC(ctx, proto_val, JS_CLASS_C_FUNCTION);
  }
  if (LEPUS_IsException(func_obj)) return func_obj;
  HandleScope func_scope(ctx, &func_obj, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_OBJ(func_obj);
  p->u.cfunc.c_function.generic = func;
  p->u.cfunc.length = length;
  p->u.cfunc.cproto = cproto;
  p->u.cfunc.magic = magic;
  p->is_constructor = (cproto == LEPUS_CFUNC_constructor ||
                       cproto == LEPUS_CFUNC_constructor_magic ||
                       cproto == LEPUS_CFUNC_constructor_or_func ||
                       cproto == LEPUS_CFUNC_constructor_or_func_magic);
  if (!name) {
    name = "";
  }
  name_atom = LEPUS_NewAtom(ctx, name);
  func_scope.PushLEPUSAtom(name_atom);
  js_function_set_properties(ctx, p, name_atom, length);
  return func_obj;
}

/* Note: at least 'length' arguments will be readable in 'argv' */
LEPUSValue JS_NewCFunction2_GC(LEPUSContext *ctx, LEPUSCFunction *func,
                               const char *name, int length,
                               LEPUSCFunctionEnum cproto, int magic) {
  return JS_NewCFunction3(ctx, func, name, length, cproto, magic,
                          ctx->function_proto);
}

LEPUSValue JS_ThrowStackOverflow_GC(LEPUSContext *ctx) {
  return LEPUS_ThrowInternalError(ctx, "stack overflow");
}

static LEPUSValue js_c_function_data_call(LEPUSContext *ctx,
                                          LEPUSValueConst func_obj,
                                          LEPUSValueConst this_val, int argc,
                                          LEPUSValueConst *argv, int flags) {
  HandleScope func_scope(ctx);
  JSCFunctionDataRecord *s = static_cast<JSCFunctionDataRecord *>(
      LEPUS_GetOpaque(func_obj, JS_CLASS_C_FUNCTION_DATA));
  LEPUSValueConst *arg_buf;
  int i;

  // <Primjs begin>
#ifdef ENABLE_VIRTUAL_STACK
  size_t arg_size = 0;
  LEPUSValue ret = LEPUS_UNDEFINED;
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
#endif
  /* XXX: could add the function on the stack for debug */
  if (unlikely(argc < s->length)) {
#ifdef ENABLE_VIRTUAL_STACK
    size_t arg_size = sizeof(arg_buf[0]) * s->length;
    arg_buf = js_get_virtual_sp(arg_size);
    if (!arg_buf) return JS_ThrowStackOverflow_GC(ctx);
#elif !defined(OS_WIN)
    arg_buf = static_cast<LEPUSValue *>(alloca(sizeof(arg_buf[0]) * s->length));
#else
    arg_buf =
        static_cast<LEPUSValue *>(_alloca(sizeof(arg_buf[0]) * s->length));
#endif
    for (i = 0; i < argc; i++) arg_buf[i] = argv[i];
    for (i = argc; i < s->length; i++) arg_buf[i] = LEPUS_UNDEFINED;
  } else {
    arg_buf = argv;
  }
  func_scope.PushLEPUSValueArrayHandle(arg_buf, s->length, false);
#ifdef ENABLE_VIRTUAL_STACK
  ret = s->func(ctx, this_val, argc, arg_buf, s->magic, s->data);
  js_pop_virtual_sp(arg_size);
  return ret;
#else
  return s->func(ctx, this_val, argc, arg_buf, s->magic, s->data);
#endif
  // <Primjs end>
}

LEPUSValue JS_NewCFunctionData_GC(LEPUSContext *ctx, LEPUSCFunctionData *func,
                                  int length, int magic, int data_len,
                                  LEPUSValueConst *data) {
  JSCFunctionDataRecord *s;
  LEPUSValue func_obj;
  int i;

  func_obj = JS_NewObjectProtoClass_GC(ctx, ctx->function_proto,
                                       JS_CLASS_C_FUNCTION_DATA);
  if (LEPUS_IsException(func_obj)) return func_obj;
  HandleScope func_scope(ctx, &func_obj, HANDLE_TYPE_LEPUS_VALUE);
  s = static_cast<JSCFunctionDataRecord *>(
      lepus_malloc(ctx, sizeof(*s) + data_len * sizeof(LEPUSValue),
                   ALLOC_TAG_JSCFunctionDataRecord));
  if (!s) {
    return LEPUS_EXCEPTION;
  }
  s->func = func;
  s->length = length;
  s->data_len = data_len;
  s->magic = magic;
  for (i = 0; i < data_len; i++) s->data[i] = data[i];
  LEPUS_SetOpaque(func_obj, s);
  js_function_set_properties(ctx, LEPUS_VALUE_GET_OBJ(func_obj),
                             JS_ATOM_empty_string, length);
  return func_obj;
}

static void free_property(LEPUSRuntime *rt, JSProperty *pr, int prop_flags) {}
#ifdef QJS_UNITTEST
static JSShapeProperty *find_own_property(
#else
QJS_STATIC force_inline JSShapeProperty *find_own_property(
#endif
    JSProperty **ppr, LEPUSObject *p, JSAtom atom) {
  JSShape *sh;
  JSShapeProperty *pr, *prop;
  intptr_t h;
  sh = p->shape;
  h = (uintptr_t)atom & sh->prop_hash_mask;
  h = sh->prop_hash_end[-h - 1];
  prop = get_shape_prop(sh);
  while (h) {
    pr = &prop[h - 1];
    if (likely(pr->atom == atom)) {
      *ppr = &p->prop[h - 1];
      /* the compiler should be able to assume that pr != NULL here */
      return pr;
    }
    h = pr->hash_next;
  }
  *ppr = NULL;
  return NULL;
}
/* indicate that the object may be part of a function prototype cycle */
static void set_cycle_flag(LEPUSContext *ctx, LEPUSValueConst obj) {}

void JS_MarkValue_GC(LEPUSRuntime *rt, LEPUSValueConst val,
                     LEPUS_MarkFunc *mark_func, int local_idx) {
  mark_func(rt, val, local_idx);
}

/* garbage collection */

void JS_RunGC_GC(LEPUSRuntime *rt) { rt->gc->CollectGarbage(); }

void LEPUS_RunAllGC() {
  pthread_mutex_lock(&runtime_mutex);
  std::unordered_set<LEPUSRuntime *> *g_rt_set = js_get_rt_set();
  for (auto it : *g_rt_set) {
    it->gc->CollectGarbage(0, true);
  }
  pthread_mutex_unlock(&runtime_mutex);
}

LEPUSValue gc(LEPUSContext *ctx, LEPUSValueConst this_val, int argc,
              LEPUSValueConst *argv) {
  ctx->rt->gc->CollectGarbage();
  return LEPUS_UNDEFINED;
}

/* Return false if not an object or if the object has already been
   freed (zombie objects are visible in finalizers when freeing
   cycles). */
static BOOL JS_IsLiveObject(LEPUSRuntime *rt, LEPUSValueConst obj) {
  LEPUSObject *p;
  if (!LEPUS_IsObject(obj)) return FALSE;
  p = LEPUS_VALUE_GET_OBJ(obj);
  return is_marked(p);
}

/* Compute memory used by various object types */
/* XXX: poor man's approach to handling multiply referenced objects */
typedef struct JSMemoryUsage_helper {
  double memory_used_count;
  double str_count;
  double str_size;
  double lepus_func_count;
  double lepus_func_size;
  double lepus_func_code_size;
  double lepus_func_pc2line_count;
  double lepus_func_pc2line_size;
} JSMemoryUsage_helper;

LEPUSValue JS_GetGlobalObject_GC(LEPUSContext *ctx) { return ctx->global_obj; }

/* WARNING: obj is freed */

#ifndef NO_QUICKJS_COMPILER

// <Primjs begin>
#endif

#define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0)

LEPUSValue JS_NewError_GC(LEPUSContext *ctx) {
  return JS_NewObjectClass_GC(ctx, JS_CLASS_ERROR);
}

/* never use it directly */
static LEPUSValue __attribute__((format(printf, 3, 4)))
__JS_ThrowSyntaxErrorAtom(LEPUSContext *ctx, JSAtom atom, const char *fmt,
                          ...) {
  char buf[ATOM_GET_STR_BUF_SIZE];
  return LEPUS_ThrowSyntaxError(ctx, fmt,
                                JS_AtomGetStr(ctx, buf, sizeof(buf), atom));
}

/* %s is replaced by 'atom'. The macro is used so that gcc can check
    the format string. */
#define JS_ThrowTypeErrorAtom(ctx, fmt, atom) \
  __JS_ThrowTypeErrorAtom(ctx, atom, fmt, "")
#define JS_ThrowSyntaxErrorAtom(ctx, fmt, atom) \
  __JS_ThrowSyntaxErrorAtom(ctx, atom, fmt, "")

static LEPUSValue JS_ThrowTypeErrorNotASymbol(LEPUSContext *ctx) {
  return LEPUS_ThrowTypeError(ctx, "not a symbol");
}

LEPUSValue JS_ThrowReferenceErrorNotDefined_GC(LEPUSContext *ctx, JSAtom name) {
  char buf[ATOM_GET_STR_BUF_SIZE];
  return LEPUS_ThrowReferenceError(ctx, "%s is not defined",
                                   JS_AtomGetStr(ctx, buf, sizeof(buf), name));
}

LEPUSValue JS_ThrowReferenceErrorUninitialized_GC(LEPUSContext *ctx,
                                                  JSAtom name) {
  char buf[ATOM_GET_STR_BUF_SIZE];
  return LEPUS_ThrowReferenceError(
      ctx, "%s is not initialized",
      name == JS_ATOM_NULL ? "lexical variable"
                           : JS_AtomGetStr(ctx, buf, sizeof(buf), name));
}

/* return -1 (exception) or TRUE/FALSE */
int JS_SetPrototypeInternal_GC(LEPUSContext *ctx, LEPUSValueConst obj,
                               LEPUSValueConst proto_val, BOOL throw_flag) {
  LEPUSObject *proto, *p, *p1;
  JSShape *sh;

  if (throw_flag) {
    if (LEPUS_VALUE_IS_NULL(obj) || LEPUS_VALUE_IS_UNDEFINED(obj)) goto not_obj;
  } else {
    if (LEPUS_VALUE_IS_NOT_OBJECT(obj)) goto not_obj;
  }
  p = LEPUS_VALUE_GET_OBJ(obj);
  if (LEPUS_VALUE_IS_NOT_OBJECT(proto_val)) {
    if (!LEPUS_VALUE_IS_NULL(proto_val)) {
    not_obj:
      JS_ThrowTypeErrorNotAnObject(ctx);
      return -1;
    }
    proto = NULL;
  } else {
    proto = LEPUS_VALUE_GET_OBJ(proto_val);
  }

  if (throw_flag && LEPUS_VALUE_IS_NOT_OBJECT(obj)) return TRUE;

  if (unlikely(p->class_id == JS_CLASS_PROXY))
    return js_proxy_setPrototypeOf(ctx, obj, proto_val, throw_flag);
  sh = p->shape;
  if (sh->proto == proto) return TRUE;
  if (!p->extensible) {
    if (throw_flag) {
      LEPUS_ThrowTypeError(ctx, "object is not extensible");
      return -1;
    } else {
      return FALSE;
    }
  }
  if (proto) {
    /* check if there is a cycle */
    p1 = proto;
    do {
      if (p1 == p) {
        if (throw_flag) {
          LEPUS_ThrowTypeError(ctx, "circular prototype chain");
          return -1;
        } else {
          return FALSE;
        }
      }
      /* Note: for Proxy objects, proto is NULL */
      p1 = p1->shape->proto;
    } while (p1 != NULL);
  }

  if (js_shape_prepare_update(ctx, p, NULL)) return -1;
  sh = p->shape;
  sh->proto = proto;
  return TRUE;
}

/* return -1 (exception) or TRUE/FALSE */
int JS_SetPrototype_GC(LEPUSContext *ctx, LEPUSValueConst obj,
                       LEPUSValueConst proto_val) {
  return JS_SetPrototypeInternal_GC(ctx, obj, proto_val, TRUE);
}

/* Return an Object, LEPUS_NULL or LEPUS_EXCEPTION in case of Proxy object. */
LEPUSValueConst JS_GetPrototype_GC(LEPUSContext *ctx, LEPUSValueConst val) {
  LEPUSObject *p;

  switch (LEPUS_VALUE_GET_NORM_TAG(val)) {
    case LEPUS_TAG_BIG_INT:
      val = ctx->class_proto[JS_CLASS_BIG_INT];
      break;
    case LEPUS_TAG_INT:
    case LEPUS_TAG_FLOAT64:
      val = ctx->class_proto[JS_CLASS_NUMBER];
      break;
    case LEPUS_TAG_BOOL:
      val = ctx->class_proto[JS_CLASS_BOOLEAN];
      break;
    case LEPUS_TAG_STRING:
    case LEPUS_TAG_SEPARABLE_STRING:
      val = ctx->class_proto[JS_CLASS_STRING];
      break;
    case LEPUS_TAG_SYMBOL:
      val = ctx->class_proto[JS_CLASS_SYMBOL];
      break;
    case LEPUS_TAG_OBJECT:
      p = LEPUS_VALUE_GET_OBJ(val);
      if (unlikely(p->class_id == JS_CLASS_PROXY)) {
        val = js_proxy_getPrototypeOf(ctx, val);
      } else {
        p = p->shape->proto;
        if (!p) {
          val = LEPUS_NULL;
        } else {
          val = LEPUS_MKPTR(LEPUS_TAG_OBJECT, p);
        }
      }
      break;
    case LEPUS_TAG_NULL:
    case LEPUS_TAG_UNDEFINED:
    default:
      val = LEPUS_NULL;
      break;
  }
  return val;
}

/* return TRUE, FALSE or (-1) in case of exception */
static int JS_OrdinaryIsInstanceOf(LEPUSContext *ctx, LEPUSValueConst val,
                                   LEPUSValueConst obj) {
  LEPUSValue obj_proto;
  LEPUSObject *proto;
  LEPUSObject *p, *proto1;
  BOOL ret;

  if (!LEPUS_IsFunction(ctx, obj)) return FALSE;
  p = LEPUS_VALUE_GET_OBJ(obj);
  if (p->class_id == JS_CLASS_BOUND_FUNCTION) {
    JSBoundFunction *s = p->u.bound_function;
    LEPUSValueConst func_obj = JSRef2Value(ctx, s->func_obj);
    HandleScope func_scope(ctx, &func_obj, HANDLE_TYPE_LEPUS_VALUE);
    return JS_IsInstanceOf_GC(ctx, val, func_obj);
  }

  /* Only explicitly boxed values are instances of constructors */
  if (LEPUS_VALUE_IS_NOT_OBJECT(val)) return FALSE;
  ret = FALSE;
  obj_proto = JS_GetPropertyInternal_GC(ctx, obj, JS_ATOM_prototype, obj, 0);
  if (LEPUS_VALUE_IS_NOT_OBJECT(obj_proto)) {
    if (!LEPUS_IsException(obj_proto))
      LEPUS_ThrowTypeError(ctx,
                           "operand 'prototype' property is not an object");
    ret = -1;
    goto done;
  }
  proto = LEPUS_VALUE_GET_OBJ(obj_proto);
  p = LEPUS_VALUE_GET_OBJ(val);
  for (;;) {
    proto1 = p->shape->proto;
    if (!proto1) {
      if (p->class_id == JS_CLASS_PROXY) {
        LEPUSValueConst proto_val;
        proto_val = JS_GetPrototype_GC(
            ctx, LEPUS_MKPTR(LEPUS_TAG_OBJECT, (LEPUSObject *)p));
        if (LEPUS_IsException(proto_val)) {
          ret = -1;
          goto done;
        }
        proto1 = LEPUS_VALUE_GET_OBJ(proto_val);
        if (!proto1) break;
      } else {
        break;
      }
    }
    p = proto1;
    if (proto == p) {
      ret = TRUE;
      break;
    }
  }
done:
  return ret;
}

/* return TRUE, FALSE or (-1) in case of exception */
int JS_IsInstanceOf_GC(LEPUSContext *ctx, LEPUSValueConst val,
                       LEPUSValueConst obj) {
  LEPUSValue method;

  if (!LEPUS_IsObject(obj)) goto fail;
  method =
      JS_GetPropertyInternal_GC(ctx, obj, JS_ATOM_Symbol_hasInstance, obj, 0);
  if (LEPUS_IsException(method)) return -1;
  if (!LEPUS_IsNull(method) && !LEPUS_IsUndefined(method)) {
    LEPUSValue ret;
    ret = JS_CallFree_GC(ctx, method, obj, 1, &val);
    return JS_ToBoolFree_GC(ctx, ret);
  }

  /* legacy case */
  if (!LEPUS_IsFunction(ctx, obj)) {
  fail:
    LEPUS_ThrowTypeError(ctx, "invalid 'instanceof' right operand");
    return -1;
  }
  return JS_OrdinaryIsInstanceOf(ctx, val, obj);
}

#ifdef QJS_UNITTEST
static LEPUSValue GetLEPUSPropertyValue(JSProperty *pr) {
  if (pr) {
    return pr->u.value;
  }
  return LEPUS_MKVAL(LEPUS_TAG_NULL, 0);
}

static uint32_t GetLEPUSShapePropertyFlags(JSShapeProperty *prs) {
  if (prs) {
    return prs->flags;
  }
  return 0;
}
#endif

/* return the value associated to the autoinit property or an exception */
typedef LEPUSValue JSAutoInitFunc(LEPUSContext *ctx, LEPUSObject *p,
                                  JSAtom atom, void *opaque);

static int JS_AutoInitProperty(LEPUSContext *ctx, LEPUSObject *p, JSAtom prop,
                               JSProperty *pr, JSShapeProperty *prs) {
  LEPUSValue val;
  JSAutoInitFunc *func;

  if (js_shape_prepare_update(ctx, p, &prs)) return -1;
  func = pr->u.init.init_func;
  /* 'func' shall not modify the object properties 'pr' */
  val = func(ctx, p, prop, pr->u.init.opaque);
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  prs->flags &= ~LEPUS_PROP_TMASK;
  pr->u.value = LEPUS_UNDEFINED;
  if (LEPUS_IsException(val)) return -1;
  pr->u.value = val;
  return 0;
}

LEPUSValue JS_GetPropertyInternal_GC(LEPUSContext *ctx, LEPUSValueConst obj,
                                     JSAtom prop, LEPUSValueConst this_obj,
                                     BOOL throw_ref_error) {
  return JS_GetPropertyInternalImpl_GC(ctx, obj, prop, this_obj,
                                       throw_ref_error);
}

LEPUSValue JS_GetPropertyInternalImpl_GC(LEPUSContext *ctx, LEPUSValueConst obj,
                                         JSAtom prop, LEPUSValueConst this_obj,
                                         BOOL throw_ref_error) {
  LEPUSObject *p = nullptr;
  JSProperty *pr;
  JSShapeProperty *prs;
  int64_t tag;
  char buf[ATOM_GET_STR_BUF_SIZE];

  tag = LEPUS_VALUE_GET_TAG(obj);
// <Primjs begin>
#ifdef ENABLE_LEPUSNG
  if (tag == LEPUS_TAG_LEPUS_REF) {
    if (ctx->rt->js_callbacks_.get_property) {
      LEPUSValue ret =
          JSRefGetProperty(ctx, obj, prop, this_obj, throw_ref_error);
      ctx->ptr_handles->PushLEPUSValuePtr(ret);
      return ret;
    }
  }
#endif
  // <Primjs end>

  if (unlikely(tag != LEPUS_TAG_OBJECT)) {
    switch (tag) {
      case LEPUS_TAG_NULL:
        // <Primjs begin>
        // if not in the strict mode, read properties from null will return null
        if (ctx->no_lepus_strict_mode) {
          return LEPUS_NULL;
        } else {
          return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of null",
                                       prop);
        }
        // <Primjs end>
      case LEPUS_TAG_UNDEFINED:
        if (ctx->no_lepus_strict_mode) {
          return LEPUS_NULL;
        } else {
          return JS_ThrowTypeErrorAtom(
              ctx, "cannot read property '%s' of undefined", prop);
        }
      case LEPUS_TAG_EXCEPTION:
        return LEPUS_EXCEPTION;
      case LEPUS_TAG_STRING: {
        JSString *p1 = LEPUS_VALUE_GET_STRING(obj);
        if (__JS_AtomIsTaggedInt(prop)) {
          uint32_t idx, ch;
          idx = __JS_AtomToUInt32(prop);
          if (idx < p1->len) {
            if (p1->is_wide_char)
              ch = p1->u.str16[idx];
            else
              ch = p1->u.str8[idx];
            LEPUSValue ret = js_new_string_char(ctx, ch);
            ctx->ptr_handles->PushLEPUSValuePtr(ret);
            return ret;
          }
        } else if (prop == JS_ATOM_length) {
          return LEPUS_NewInt32(ctx, p1->len);
        }
      } break;
      case LEPUS_TAG_SEPARABLE_STRING: {
        auto *separable_string = JS_GetSeparableString(obj);
        if (prop == JS_ATOM_length) {
          return LEPUS_NewInt32(ctx, separable_string->len);
        } else if (__JS_AtomIsTaggedInt(prop)) {
          uint32_t idx, ch;
          idx = __JS_AtomToUInt32(prop);
          if (idx < separable_string->len) {
            LEPUSValue str = JS_GetSeparableStringContentNotDup_GC(ctx, obj);
            HandleScope block_scope(ctx, &str, HANDLE_TYPE_LEPUS_VALUE);
            return JS_GetPropertyInternalImpl_GC(ctx, str, prop, this_obj,
                                                 throw_ref_error);
          }
        }
      } break;
      default:
        break;
    }
    /* cannot raise an exception */
    p = LEPUS_VALUE_GET_OBJ(JS_GetPrototype_GC(ctx, obj));
    if (!p) return LEPUS_UNDEFINED;
  } else {
    p = LEPUS_VALUE_GET_OBJ(obj);
  }

  for (;;) {
    prs = find_own_property(&pr, p, prop);
    if (prs) {
      /* found */
      if (unlikely(prs->flags & LEPUS_PROP_TMASK)) {
        if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_GETSET) {
          if (unlikely(!pr->u.getset.getter)) {
            return LEPUS_UNDEFINED;
          } else {
            LEPUSValue func =
                LEPUS_MKPTR(LEPUS_TAG_OBJECT, pr->u.getset.getter);
            LEPUSValue ret = JS_CallFree_GC(ctx, func, this_obj, 0, NULL);
            ctx->ptr_handles->PushLEPUSValuePtr(ret);
            return ret;
          }
        } else if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_VARREF) {
          LEPUSValue val = *pr->u.var_ref->pvalue;
          if (unlikely(LEPUS_IsUninitialized(val)))
            return JS_ThrowReferenceErrorUninitialized_GC(ctx, prs->atom);
          return val;
        } else if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_AUTOINIT) {
          /* Instantiate property and retry */
          if (JS_AutoInitProperty(ctx, p, prop, pr, prs))
            return LEPUS_EXCEPTION;
          continue;
        }
      } else {
        return pr->u.value;
      }
    }
    if (unlikely(p->is_exotic)) {
      /* exotic behaviors */
      if (p->fast_array) {
        if (__JS_AtomIsTaggedInt(prop)) {
          uint32_t idx = __JS_AtomToUInt32(prop);
          if (idx < p->u.array.count) {
            /* we avoid duplicating the code */
            return JS_GetPropertyUint32_GC(
                ctx, LEPUS_MKPTR(LEPUS_TAG_OBJECT, p), idx);
          } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
                     p->class_id <= JS_CLASS_BIG_UINT64_ARRAY) {
            goto typed_array_oob;
          }
        } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
                   p->class_id <= JS_CLASS_BIG_UINT64_ARRAY) {
          int ret;
          ret = JS_AtomIsNumericIndex(ctx, prop);
          if (ret != 0) {
            if (ret < 0) return LEPUS_EXCEPTION;
          typed_array_oob:
            /* when array is detached, return undefined */
            return LEPUS_UNDEFINED;
          }
        }
      } else {
        const LEPUSClassExoticMethods *em =
            ctx->rt->class_array[p->class_id].exotic;
        if (em) {
          if (em->get_property) {
            /* XXX: should pass throw_ref_error */
            return em->get_property(ctx, LEPUS_MKPTR(LEPUS_TAG_OBJECT, p), prop,
                                    this_obj);
          }
          if (em->get_own_property) {
            LEPUSPropertyDescriptor desc;
            int ret;

            ret = em->get_own_property(ctx, &desc,
                                       LEPUS_MKPTR(LEPUS_TAG_OBJECT, p), prop);
            if (ret < 0) return LEPUS_EXCEPTION;
            if (ret) {
              if (desc.flags & LEPUS_PROP_GETSET) {
                return JS_CallFree_GC(ctx, desc.getter, this_obj, 0, NULL);
              } else {
                ctx->ptr_handles->PushLEPUSValuePtr(desc.value);
                return desc.value;
              }
            }
          }
        }
      }
    }
    p = p->shape->proto;
    if (!p) break;
  }
  if (unlikely(throw_ref_error)) {
    return JS_ThrowReferenceErrorNotDefined_GC(ctx, prop);
  } else {
    return LEPUS_UNDEFINED;
  }
}

/* Private fields can be added even on non extensible objects or
   Proxies */
int JS_DefinePrivateField_GC(LEPUSContext *ctx, LEPUSValueConst obj,
                             LEPUSValueConst name, LEPUSValue val) {
  LEPUSObject *p;
  JSShapeProperty *prs;
  JSProperty *pr;
  JSAtom prop;
  HandleScope func_scope(ctx->rt);

  if (unlikely(LEPUS_VALUE_IS_NOT_OBJECT(obj))) {
    JS_ThrowTypeErrorNotAnObject(ctx);
    goto fail;
  }
  /* safety check */
  if (unlikely(!LEPUS_VALUE_IS_SYMBOL(name))) {
    JS_ThrowTypeErrorNotASymbol(ctx);
    goto fail;
  }
  prop = js_symbol_to_atom(ctx, (LEPUSValue)name);
  func_scope.PushLEPUSAtom(prop);
  p = LEPUS_VALUE_GET_OBJ(obj);
  prs = find_own_property(&pr, p, prop);
  if (prs) {
    JS_ThrowTypeErrorAtom(ctx, "private class field '%s' already exists", prop);
    goto fail;
  }
  pr = add_property_gc(ctx, p, prop, LEPUS_PROP_C_W_E);
  if (unlikely(!pr)) {
  fail:
    return -1;
  }
  pr->u.value = val;
  return 0;
}

LEPUSValue JS_GetPrivateField_GC(LEPUSContext *ctx, LEPUSValueConst obj,
                                 LEPUSValueConst name) {
  LEPUSObject *p;
  JSShapeProperty *prs;
  JSProperty *pr;
  JSAtom prop;

  if (unlikely(LEPUS_VALUE_IS_NOT_OBJECT(obj)))
    return JS_ThrowTypeErrorNotAnObject(ctx);
  /* safety check */
  if (unlikely(!LEPUS_VALUE_IS_SYMBOL(name)))
    return JS_ThrowTypeErrorNotASymbol(ctx);
  // unnecessary to push into handle, life-cycle following name
  prop = js_symbol_to_atom(ctx, (LEPUSValue)name);
  p = LEPUS_VALUE_GET_OBJ(obj);
  prs = find_own_property(&pr, p, prop);
  if (!prs) {
    JS_ThrowTypeErrorPrivateNotFound(ctx, prop);
    return LEPUS_EXCEPTION;
  }
  return pr->u.value;
}

int JS_SetPrivateField_GC(LEPUSContext *ctx, LEPUSValueConst obj,
                          LEPUSValueConst name, LEPUSValue val) {
  LEPUSObject *p;
  JSShapeProperty *prs;
  JSProperty *pr;
  JSAtom prop;

  if (unlikely(LEPUS_VALUE_IS_NOT_OBJECT(obj))) {
    JS_ThrowTypeErrorNotAnObject(ctx);
    goto fail;
  }
  /* safety check */
  if (unlikely(!LEPUS_VALUE_IS_SYMBOL(name))) {
    JS_ThrowTypeErrorNotASymbol(ctx);
    goto fail;
  }
  // unnecessary to push into handle, life-cycle following name
  prop = js_symbol_to_atom(ctx, (LEPUSValue)name);
  p = LEPUS_VALUE_GET_OBJ(obj);
  prs = find_own_property(&pr, p, prop);
  if (!prs) {
    JS_ThrowTypeErrorPrivateNotFound(ctx, prop);
  fail:
    return -1;
  }
  set_value_gc(ctx, &pr->u.value, val);
  return 0;
}

int JS_AddBrand_GC(LEPUSContext *ctx, LEPUSValueConst obj,
                   LEPUSValueConst home_obj) {
  LEPUSObject *p, *p1;
  JSShapeProperty *prs;
  JSProperty *pr;
  LEPUSValue brand = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &brand, HANDLE_TYPE_LEPUS_VALUE);
  JSAtom brand_atom;

  if (unlikely(LEPUS_VALUE_IS_NOT_OBJECT(home_obj))) {
    JS_ThrowTypeErrorNotAnObject(ctx);
    return -1;
  }
  p = LEPUS_VALUE_GET_OBJ(home_obj);
  prs = find_own_property(&pr, p, JS_ATOM_Private_brand);
  if (!prs) {
    brand = JS_NewSymbolFromAtom_GC(ctx, JS_ATOM_brand, JS_ATOM_TYPE_PRIVATE);
    if (LEPUS_IsException(brand)) return -1;
    /* if the brand is not present, add it */
    pr = add_property_gc(ctx, p, JS_ATOM_Private_brand, LEPUS_PROP_C_W_E);
    if (!pr) {
      return -1;
    }
    pr->u.value = brand;
  } else {
    brand = pr->u.value;
  }
  // unnecessary to push into handle, life-cycle following brand_atom
  brand_atom = js_symbol_to_atom(ctx, brand);

  if (unlikely(LEPUS_VALUE_IS_NOT_OBJECT(obj))) {
    JS_ThrowTypeErrorNotAnObject(ctx);
    return -1;
  }
  p1 = LEPUS_VALUE_GET_OBJ(obj);
  pr = add_property_gc(ctx, p1, brand_atom, LEPUS_PROP_C_W_E);
  if (!pr) return -1;
  pr->u.value = LEPUS_UNDEFINED;
  return 0;
}

int JS_CheckBrand_GC(LEPUSContext *ctx, LEPUSValueConst obj,
                     LEPUSValueConst func) {
  LEPUSObject *p, *p1, *home_obj;
  JSShapeProperty *prs;
  JSProperty *pr;
  LEPUSValueConst brand = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &brand, HANDLE_TYPE_LEPUS_VALUE);

  /* get the home object of 'func' */
  if (unlikely(LEPUS_VALUE_IS_NOT_OBJECT(func))) {
  not_obj:
    JS_ThrowTypeErrorNotAnObject(ctx);
    return -1;
  }
  p1 = LEPUS_VALUE_GET_OBJ(func);
  if (!lepus_class_has_bytecode(p1->class_id)) goto not_obj;
  home_obj = p1->u.func.home_object;
  if (!home_obj) goto not_obj;
  prs = find_own_property(&pr, home_obj, JS_ATOM_Private_brand);
  if (!prs) {
    LEPUS_ThrowTypeError(ctx, "expecting <brand> private field");
    return -1;
  }
  brand = pr->u.value;
  /* safety check */
  if (unlikely(!LEPUS_VALUE_IS_SYMBOL(brand))) goto not_obj;

  /* get the brand array of 'obj' */
  if (unlikely(LEPUS_VALUE_IS_NOT_OBJECT(obj))) goto not_obj;
  p = LEPUS_VALUE_GET_OBJ(obj);
  prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, (LEPUSValue)brand));
  if (!prs) {
    LEPUS_ThrowTypeError(ctx, "invalid brand on object");
    return -1;
  }
  return 0;
}

static int num_keys_cmp(const void *p1, const void *p2, void *opaque) {
  LEPUSContext *ctx = static_cast<LEPUSContext *>(opaque);
  JSAtom atom1 = ((const LEPUSPropertyEnum *)p1)->atom;
  JSAtom atom2 = ((const LEPUSPropertyEnum *)p2)->atom;
  uint32_t v1, v2;
  BOOL atom1_is_integer, atom2_is_integer;

  atom1_is_integer = JS_AtomIsArrayIndex(ctx, &v1, atom1);
  atom2_is_integer = JS_AtomIsArrayIndex(ctx, &v2, atom2);
  assert(atom1_is_integer && atom2_is_integer);
  if (v1 < v2)
    return -1;
  else if (v1 == v2)
    return 0;
  else
    return 1;
}

/* return < 0 in case if exception, 0 if OK. ptab and its atoms must
   be freed by the user. */
static int __exception JS_GetOwnPropertyNamesInternal(LEPUSContext *ctx,
                                                      LEPUSPropertyEnum **ptab,
                                                      uint32_t *plen,
                                                      LEPUSObject *p,
                                                      int flags) {
  int i, j;
  JSShape *sh;
  JSShapeProperty *prs;
  LEPUSPropertyEnum *tab_atom = nullptr, *tab_exotic = nullptr;
  HandleScope func_scope(ctx, &tab_atom, HANDLE_TYPE_HEAP_OBJ);
  func_scope.PushHandle(&tab_exotic, HANDLE_TYPE_HEAP_OBJ);
  JSAtom atom;
  uint32_t num_keys_count, str_keys_count, sym_keys_count, atom_count;
  uint32_t num_index, str_index, sym_index, exotic_count;
  BOOL is_enumerable, num_sorted;
  uint32_t num_key;
  JSAtomKindEnum kind;

  /* clear pointer for consistency in case of failure */
  *ptab = NULL;
  *plen = 0;

  /* compute the number of returned properties */
  num_keys_count = 0;
  str_keys_count = 0;
  sym_keys_count = 0;
  exotic_count = 0;
  tab_exotic = NULL;
  // atom && shape && prs, these objects have the same life cycle as p
  sh = p->shape;
  for (i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) {
    atom = prs->atom;
    if (atom != JS_ATOM_NULL) {
      is_enumerable = ((prs->flags & LEPUS_PROP_ENUMERABLE) != 0);
      kind = JS_AtomGetKind(ctx, atom);
      if ((!(flags & LEPUS_GPN_ENUM_ONLY) || is_enumerable) &&
          ((flags >> kind) & 1) != 0) {
        /* need to raise an exception in case of the module
           name space (implicit GetOwnProperty) */
        if (unlikely((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_VARREF) &&
            (flags & (LEPUS_GPN_SET_ENUM | LEPUS_GPN_ENUM_ONLY))) {
          JSVarRef *var_ref = p->prop[i].u.var_ref;
          if (unlikely(LEPUS_IsUninitialized(*var_ref->pvalue))) {
            JS_ThrowReferenceErrorUninitialized_GC(ctx, prs->atom);
            return -1;
          }
        }
        if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) {
          num_keys_count++;
        } else if (kind == JS_ATOM_KIND_STRING) {
          str_keys_count++;
        } else {
          sym_keys_count++;
        }
      }
    }
  }

  if (p->is_exotic) {
    if (p->fast_array) {
      /* the implicit GetOwnProperty raises an exception if the
         typed array is detached */
      if ((flags & (LEPUS_GPN_SET_ENUM | LEPUS_GPN_ENUM_ONLY)) &&
          (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
           p->class_id <= JS_CLASS_BIG_UINT64_ARRAY) &&
          typed_array_is_detached(ctx, p) &&
          typed_array_get_length(ctx, p) != 0) {
        JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
        return -1;
      }
      num_keys_count += p->u.array.count;
    } else {
      const LEPUSClassExoticMethods *em =
          ctx->rt->class_array[p->class_id].exotic;
      if (em && em->get_own_property_names) {
        if (em->get_own_property_names(ctx, &tab_exotic, &exotic_count,
                                       LEPUS_MKPTR(LEPUS_TAG_OBJECT, p)))
          return -1;
        for (i = 0; i < exotic_count; i++) {
          atom = tab_exotic[i].atom;
          kind = JS_AtomGetKind(ctx, atom);
          if (((flags >> kind) & 1) != 0) {
            is_enumerable = FALSE;
            if (flags & (LEPUS_GPN_SET_ENUM | LEPUS_GPN_ENUM_ONLY)) {
              LEPUSPropertyDescriptor desc;
              int res;
              /* set the "is_enumerable" field if necessary */
              res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom);
              if (res < 0) {
                return -1;
              }
              if (res) {
                is_enumerable = ((desc.flags & LEPUS_PROP_ENUMERABLE) != 0);
              }
              tab_exotic[i].is_enumerable = is_enumerable;
            }
            if (!(flags & LEPUS_GPN_ENUM_ONLY) || is_enumerable) {
              if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) {
                num_keys_count++;
              } else if (kind == JS_ATOM_KIND_STRING) {
                str_keys_count++;
              } else {
                sym_keys_count++;
              }
            }
          }
        }
      }
    }
  }

  /* fill them */

  atom_count = num_keys_count + str_keys_count + sym_keys_count;
  if (atom_count > JS_ATOM_MAX_INT) {
    LEPUS_ThrowRangeError(ctx, "Too many properties is to enumerate");
    return -1;
  }
  /* avoid allocating 0 bytes */
  tab_atom = static_cast<LEPUSPropertyEnum *>(
      lepus_mallocz(ctx, sizeof(tab_atom[0]) * max_int(atom_count, 1),
                    ALLOC_TAG_LEPUSPropertyEnum));
  if (!tab_atom) {
    return -1;
  }
  set_heap_obj_len(tab_atom, max_int(atom_count, 1));

  num_index = 0;
  str_index = num_keys_count;
  sym_index = str_index + str_keys_count;

  num_sorted = TRUE;
  sh = p->shape;
  for (i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) {
    atom = prs->atom;
    if (atom != JS_ATOM_NULL) {
      is_enumerable = ((prs->flags & LEPUS_PROP_ENUMERABLE) != 0);
      kind = JS_AtomGetKind(ctx, atom);
      if ((!(flags & LEPUS_GPN_ENUM_ONLY) || is_enumerable) &&
          ((flags >> kind) & 1) != 0) {
        if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) {
          j = num_index++;
          num_sorted = FALSE;
        } else if (kind == JS_ATOM_KIND_STRING) {
          j = str_index++;
        } else {
          j = sym_index++;
        }
        tab_atom[j].atom = atom;
        tab_atom[j].is_enumerable = is_enumerable;
      }
    }
  }

  if (p->is_exotic) {
    if (p->fast_array) {
      for (i = 0; i < p->u.array.count; i++) {
        tab_atom[num_index].atom = __JS_AtomFromUInt32(i);
        if (tab_atom[num_index].atom == JS_ATOM_NULL) {
          return -1;
        }
        tab_atom[num_index].is_enumerable = TRUE;
        num_index++;
      }
    }
    if (exotic_count > 0) {
      for (i = 0; i < exotic_count; i++) {
        atom = tab_exotic[i].atom;
        is_enumerable = tab_exotic[i].is_enumerable;
        kind = JS_AtomGetKind(ctx, atom);
        if ((!(flags & LEPUS_GPN_ENUM_ONLY) || is_enumerable) &&
            ((flags >> kind) & 1) != 0) {
          if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) {
            j = num_index++;
            num_sorted = FALSE;
          } else if (kind == JS_ATOM_KIND_STRING) {
            j = str_index++;
          } else {
            j = sym_index++;
          }
          tab_atom[j].atom = atom;
          tab_atom[j].is_enumerable = is_enumerable;
        }
      }
    }
  }

  assert(num_index == num_keys_count);
  assert(str_index == num_keys_count + str_keys_count);
  assert(sym_index == atom_count);

  if (num_keys_count != 0 && !num_sorted) {
    rqsort(tab_atom, num_keys_count, sizeof(tab_atom[0]), num_keys_cmp, ctx);
  }
  *ptab = tab_atom;
  *plen = atom_count;
  return 0;
}

int JS_GetOwnPropertyNames_GC(LEPUSContext *ctx, LEPUSPropertyEnum **ptab,
                              uint32_t *plen, LEPUSValueConst obj, int flags) {
  if (LEPUS_VALUE_IS_NOT_OBJECT(obj)) {
    JS_ThrowTypeErrorNotAnObject(ctx);
    return -1;
  }
  return JS_GetOwnPropertyNamesInternal(ctx, ptab, plen,
                                        LEPUS_VALUE_GET_OBJ(obj), flags);
}

/* Return -1 if exception,
   FALSE if the property does not exist, TRUE if it exists. If TRUE is
   returned, the property descriptor 'desc' is filled present. */
QJS_STATIC int JS_GetOwnPropertyInternal(LEPUSContext *ctx,
                                         LEPUSPropertyDescriptor *desc,
                                         LEPUSObject *p, JSAtom prop) {
  JSShapeProperty *prs;
  JSProperty *pr;

retry:
  prs = find_own_property(&pr, p, prop);
  if (prs) {
    if (desc) {
      desc->flags = prs->flags & LEPUS_PROP_C_W_E;
      desc->getter = LEPUS_UNDEFINED;
      desc->setter = LEPUS_UNDEFINED;
      desc->value = LEPUS_UNDEFINED;
      if (unlikely(prs->flags & LEPUS_PROP_TMASK)) {
        if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_GETSET) {
          desc->flags |= LEPUS_PROP_GETSET;
          if (pr->u.getset.getter)
            desc->getter = LEPUS_MKPTR(LEPUS_TAG_OBJECT, pr->u.getset.getter);
          if (pr->u.getset.setter)
            desc->setter = LEPUS_MKPTR(LEPUS_TAG_OBJECT, pr->u.getset.setter);
        } else if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_VARREF) {
          LEPUSValue val = *pr->u.var_ref->pvalue;
          if (unlikely(LEPUS_IsUninitialized(val))) {
            JS_ThrowReferenceErrorUninitialized_GC(ctx, prs->atom);
            return -1;
          }
          desc->value = val;
        } else if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_AUTOINIT) {
          /* Instantiate property and retry */
          if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) return -1;
          goto retry;
        }
      } else {
        desc->value = pr->u.value;
      }
    } else {
      /* for consistency, send the exception even if desc is NULL */
      if (unlikely((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_VARREF)) {
        if (unlikely(LEPUS_IsUninitialized(*pr->u.var_ref->pvalue))) {
          JS_ThrowReferenceErrorUninitialized_GC(ctx, prs->atom);
          return -1;
        }
      } else if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_AUTOINIT) {
        /* nothing to do: delay instantiation until actual value and/or
         * attributes are read */
      }
    }
    return TRUE;
  }
  if (p->is_exotic) {
    if (p->fast_array) {
      /* specific case for fast arrays */
      if (__JS_AtomIsTaggedInt(prop)) {
        uint32_t idx;
        idx = __JS_AtomToUInt32(prop);
        if (idx < p->u.array.count) {
          if (desc) {
            desc->flags = LEPUS_PROP_WRITABLE | LEPUS_PROP_ENUMERABLE |
                          LEPUS_PROP_CONFIGURABLE;
            desc->getter = LEPUS_UNDEFINED;
            desc->setter = LEPUS_UNDEFINED;
            desc->value = JS_GetPropertyUint32_GC(
                ctx, LEPUS_MKPTR(LEPUS_TAG_OBJECT, p), idx);
          }
          return TRUE;
        }
      }
      /* 10.4.5.9: If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, return
       * false.*/
      // if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
      //     p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
      //   int ret;
      //   ret = JS_AtomIsNumericIndex(ctx, prop);
      //   if (ret != 0) {
      //     if (ret < 0) return -1;
      //     if (typed_array_is_detached(ctx, p)) {
      //       JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
      //       return -1;
      //     }
      //   }
      // }
    } else {
      const LEPUSClassExoticMethods *em =
          ctx->rt->class_array[p->class_id].exotic;
      if (em && em->get_own_property) {
        return em->get_own_property(ctx, desc, LEPUS_MKPTR(LEPUS_TAG_OBJECT, p),
                                    prop);
      }
    }
  }
  return FALSE;
}

int JS_GetOwnProperty_GC(LEPUSContext *ctx, LEPUSPropertyDescriptor *desc,
                         LEPUSValueConst obj, JSAtom prop) {
  if (LEPUS_VALUE_IS_NOT_OBJECT(obj)) {
    JS_ThrowTypeErrorNotAnObject(ctx);
    return -1;
  }
  return JS_GetOwnPropertyInternal(ctx, desc, LEPUS_VALUE_GET_OBJ(obj), prop);
}

/* return -1 if exception (Proxy object only) or TRUE/FALSE */
int JS_IsExtensible_GC(LEPUSContext *ctx, LEPUSValueConst obj) {
  LEPUSObject *p;

  if (unlikely(LEPUS_VALUE_IS_NOT_OBJECT(obj))) return FALSE;
  p = LEPUS_VALUE_GET_OBJ(obj);
  if (unlikely(p->class_id == JS_CLASS_PROXY))
    return js_proxy_isExtensible(ctx, obj);
  else
    return p->extensible;
}

/* return -1 if exception (Proxy object only) or TRUE/FALSE */
int JS_PreventExtensions_GC(LEPUSContext *ctx, LEPUSValueConst obj) {
  LEPUSObject *p;

  if (unlikely(LEPUS_VALUE_IS_NOT_OBJECT(obj))) return FALSE;
  p = LEPUS_VALUE_GET_OBJ(obj);
  if (unlikely(p->class_id == JS_CLASS_PROXY))
    return js_proxy_preventExtensions(ctx, obj);
  p->extensible = FALSE;
  return TRUE;
}

/* return -1 if exception otherwise TRUE or FALSE */
int JS_HasProperty_GC(LEPUSContext *ctx, LEPUSValueConst obj, JSAtom prop) {
  LEPUSObject *p;
  int ret;
  // <Primjs begin>

#ifdef ENABLE_LEPUSNG
  if (LEPUS_IsLepusRef(obj)) {
    return JSRefHasProperty(ctx, obj, prop);
  }
#endif

  // <Primjs end>
  if (unlikely(LEPUS_VALUE_IS_NOT_OBJECT(obj))) return FALSE;
  p = LEPUS_VALUE_GET_OBJ(obj);
  for (;;) {
    if (p->is_exotic) {
      const LEPUSClassExoticMethods *em =
          ctx->rt->class_array[p->class_id].exotic;
      if (em && em->has_property)
        return em->has_property(ctx, LEPUS_MKPTR(LEPUS_TAG_OBJECT, p), prop);
    }
    ret = JS_GetOwnPropertyInternal(ctx, NULL, p, prop);
    if (ret != 0) return ret;
    if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
        p->class_id <= JS_CLASS_BIG_UINT64_ARRAY) {
      ret = JS_AtomIsNumericIndex(ctx, prop);
      if (ret != 0) {
        if (ret < 0) return -1;
        /* the detached array test was done in
           JS_GetOwnPropertyInternal() */
        return FALSE;
      }
    }
    p = p->shape->proto;
    if (!p) break;
  }
  return FALSE;
}

/* return JS_ATOM_NULL in case of exception */
JSAtom js_value_to_atom_gc(LEPUSContext *ctx, LEPUSValueConst val) {
  JSAtom atom;
  if (LEPUS_VALUE_IS_INT(val) &&
      (uint32_t)LEPUS_VALUE_GET_INT(val) <= JS_ATOM_MAX_INT) {
    /* fast path for integer values */
    atom = __JS_AtomFromUInt32(LEPUS_VALUE_GET_INT(val));
  } else if (LEPUS_VALUE_IS_SYMBOL(val)) {
    JSAtomStruct *p = static_cast<JSAtomStruct *>(LEPUS_VALUE_GET_PTR(val));
    atom = js_get_atom_index(ctx->rt, p);
  } else {
    HandleScope block_scope(ctx->rt);
    LEPUSValue str;
    str = JS_ToPropertyKey_GC(ctx, val);
    if (LEPUS_IsException(str)) return JS_ATOM_NULL;
    block_scope.PushHandle(&str, HANDLE_TYPE_LEPUS_VALUE);
    if (LEPUS_VALUE_IS_SYMBOL(str)) {
      atom = js_symbol_to_atom(ctx, str);
    } else {
      atom = JS_NewAtomStr(ctx, LEPUS_VALUE_GET_STRING(str));
    }
  }
  return atom;
}

LEPUSValue JS_GetPropertyValue_GC(LEPUSContext *ctx, LEPUSValueConst this_obj,
                                  LEPUSValue prop) {
  JSAtom atom;
  LEPUSValue ret;

  if (likely(LEPUS_VALUE_IS_OBJECT(this_obj) && LEPUS_VALUE_IS_INT(prop))) {
    LEPUSObject *p;
    uint32_t idx;
    /* fast path for array access */
    p = LEPUS_VALUE_GET_OBJ(this_obj);
    idx = LEPUS_VALUE_GET_INT(prop);
    switch (p->class_id) {
      case JS_CLASS_ARRAY:
      case JS_CLASS_ARGUMENTS:
        if (unlikely(idx >= p->u.array.count)) goto slow_path;
        return p->u.array.u.values[idx];
      case JS_CLASS_INT8_ARRAY:
        if (unlikely(idx >= p->u.array.count)) goto slow_path;
        return LEPUS_NewInt32(ctx, p->u.array.u.int8_ptr[idx]);
      case JS_CLASS_UINT8C_ARRAY:
      case JS_CLASS_UINT8_ARRAY:
        if (unlikely(idx >= p->u.array.count)) goto slow_path;
        return LEPUS_NewInt32(ctx, p->u.array.u.uint8_ptr[idx]);
      case JS_CLASS_INT16_ARRAY:
        if (unlikely(idx >= p->u.array.count)) goto slow_path;
        return LEPUS_NewInt32(ctx, p->u.array.u.int16_ptr[idx]);
      case JS_CLASS_UINT16_ARRAY:
        if (unlikely(idx >= p->u.array.count)) goto slow_path;
        return LEPUS_NewInt32(ctx, p->u.array.u.uint16_ptr[idx]);
      case JS_CLASS_INT32_ARRAY:
        if (unlikely(idx >= p->u.array.count)) goto slow_path;
        return LEPUS_NewInt32(ctx, p->u.array.u.int32_ptr[idx]);
      case JS_CLASS_UINT32_ARRAY:
        if (unlikely(idx >= p->u.array.count)) goto slow_path;
        return JS_NewUint32(ctx, p->u.array.u.uint32_ptr[idx]);
      case JS_CLASS_BIG_INT64_ARRAY:
        if (unlikely(idx >= p->u.array.count)) goto slow_path;
        return LEPUS_NewBigInt64(ctx, p->u.array.u.int64_ptr[idx]);
      case JS_CLASS_BIG_UINT64_ARRAY:
        if (unlikely(idx >= p->u.array.count)) goto slow_path;
        return LEPUS_NewBigUint64(ctx, p->u.array.u.uint64_ptr[idx]);
      case JS_CLASS_FLOAT32_ARRAY:
        if (unlikely(idx >= p->u.array.count)) goto slow_path;
        return __JS_NewFloat64(ctx, p->u.array.u.float_ptr[idx]);
      case JS_CLASS_FLOAT64_ARRAY:
        if (unlikely(idx >= p->u.array.count)) goto slow_path;
        return __JS_NewFloat64(ctx, p->u.array.u.double_ptr[idx]);
      default:
        goto slow_path;
    }
  } else {
  slow_path:
    atom = js_value_to_atom_gc(ctx, prop);
    if (unlikely(atom == JS_ATOM_NULL)) return LEPUS_EXCEPTION;
    HandleScope block_scope(ctx->rt);
    block_scope.PushLEPUSAtom(atom);
    ret = JS_GetPropertyInternal_GC(ctx, this_obj, atom, this_obj, 0);
    return ret;
  }
}

LEPUSValue JS_GetPropertyUint32_GC(LEPUSContext *ctx, LEPUSValueConst this_obj,
                                   uint32_t idx) {
  return JS_GetPropertyValue_GC(ctx, this_obj, JS_NewUint32(ctx, idx));
}

/* Check if an object has a generalized numeric property. Return value:
   -1 for exception,
   TRUE if property exists, stored into *pval,
   FALSE if proprty does not exist.
 */
static int JS_TryGetPropertyInt64(LEPUSContext *ctx, LEPUSValueConst obj,
                                  int64_t idx, LEPUSValue *pval) {
  LEPUSValue val = LEPUS_UNDEFINED;
  JSAtom prop;
  int present;

  if (likely((uint64_t)idx <= JS_ATOM_MAX_INT)) {
    /* fast path */
    present = JS_HasProperty_GC(ctx, obj, __JS_AtomFromUInt32(idx));
    if (present > 0) {
      val = JS_GetPropertyValue_GC(ctx, obj, LEPUS_NewInt32(ctx, idx));
      if (unlikely(LEPUS_IsException(val))) present = -1;
    }
  } else {
    prop = JS_NewAtomInt64(ctx, idx);
    present = -1;
    if (likely(prop != JS_ATOM_NULL)) {
      HandleScope func_scope(ctx->rt);
      func_scope.PushLEPUSAtom(prop);
      present = JS_HasProperty_GC(ctx, obj, prop);
      if (present > 0) {
        val = JS_GetPropertyInternal_GC(ctx, obj, prop, obj, 0);
        if (unlikely(LEPUS_IsException(val))) present = -1;
      }
    }
  }
  *pval = val;
  return present;
}

static LEPUSValue JS_GetPropertyInt64(LEPUSContext *ctx, LEPUSValueConst obj,
                                      int64_t idx) {
  JSAtom prop;
  LEPUSValue val;

  if ((uint64_t)idx <= INT32_MAX) {
    /* fast path for fast arrays */
    return JS_GetPropertyValue_GC(ctx, obj, LEPUS_NewInt32(ctx, idx));
  }
  prop = JS_NewAtomInt64(ctx, idx);
  if (prop == JS_ATOM_NULL) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx->rt);
  func_scope.PushLEPUSAtom(prop);
  val = JS_GetPropertyInternal_GC(ctx, obj, prop, obj, 0);
  return val;
}

LEPUSValue JS_GetPropertyStr_GC(LEPUSContext *ctx, LEPUSValueConst this_obj,
                                const char *prop) {
  JSAtom atom;
  LEPUSValue ret;
  atom = LEPUS_NewAtom(ctx, prop);
  HandleScope func_scope(ctx->rt);
  func_scope.PushLEPUSAtom(atom);
  ret = JS_GetPropertyInternal_GC(ctx, this_obj, atom, this_obj, 0);
  return ret;
}

/* Note: the property value is not initialized. Return NULL if memory
   error. */
JSProperty *add_property_gc(LEPUSContext *ctx, LEPUSObject *p, JSAtom prop,
                            int prop_flags) {
  JSShape *sh, *new_sh;

  sh = p->shape;
  if (sh->is_hashed) {
    /* try to find an existing shape */
    new_sh = find_hashed_shape_prop(ctx->rt, sh, prop, prop_flags);
    if (new_sh) {
      HandleScope block_scope(ctx->rt);
      block_scope.PushHandle(get_alloc_from_shape(new_sh),
                             HANDLE_TYPE_DIR_HEAP_OBJ);
      /* matching shape found: use it */
      /*  the property array may need to be resized */
      if (new_sh->prop_size != sh->prop_size) {
        JSProperty *new_prop;
        new_prop = static_cast<JSProperty *>(
            lepus_realloc(ctx, p->prop, sizeof(p->prop[0]) * new_sh->prop_size,
                          ALLOC_TAG_WITHOUT_PTR));
        if (!new_prop) return NULL;
        p->prop = new_prop;
      }
      p->shape = js_dup_shape(new_sh);
      js_free_shape(ctx->rt, sh);
      return &p->prop[new_sh->prop_count - 1];
    } else if (sh->header.ref_count != 1) {
      /* if the shape is shared, clone it */
      new_sh = js_clone_shape(ctx, sh);
      if (!new_sh) return NULL;
      /* hash the cloned shape */
      new_sh->is_hashed = TRUE;
      js_shape_hash_link(ctx->rt, new_sh);
      js_free_shape(ctx->rt, p->shape);
      p->shape = new_sh;
    }
  }
  assert(p->shape->header.ref_count == 1);
  if (add_shape_property(ctx, &p->shape, p, prop, prop_flags)) return NULL;
  return &p->prop[p->shape->prop_count - 1];
}

/* can be called on Array or Arguments objects. return < 0 if
   memory alloc error. */
static no_inline __exception int convert_fast_array_to_array(LEPUSContext *ctx,
                                                             LEPUSObject *p) {
  JSProperty *pr;
  JSShape *sh;
  LEPUSValue *tab;
  uint32_t i, len, new_count;

  if (js_shape_prepare_update(ctx, p, NULL)) return -1;
  len = p->u.array.count;
  /* resize the properties once to simplify the error handling */
  sh = p->shape;
  new_count = sh->prop_count + len;
  if (new_count > sh->prop_size) {
    if (resize_properties(ctx, &p->shape, p, new_count)) return -1;
  }

  tab = p->u.array.u.values;
  for (i = 0; i < len; i++) {
    /* add_property_gc cannot fail here but
       __JS_AtomFromUInt32(i) fails for i > INT32_MAX */
    pr = add_property_gc(ctx, p, __JS_AtomFromUInt32(i), LEPUS_PROP_C_W_E);
    pr->u.value = *tab++;
  }
  p->u.array.count = 0;
  p->u.array.u.values = NULL; /* fail safe */
  p->u.array.u1.size = 0;
  p->fast_array = 0;
  return 0;
}

static int delete_property(LEPUSContext *ctx, LEPUSObject *p, JSAtom atom) {
  JSShape *sh;
  JSShapeProperty *pr, *lpr, *prop;
  JSProperty *pr1;
  uint32_t lpr_idx;
  intptr_t h, h1;

redo:
  sh = p->shape;
  h1 = atom & sh->prop_hash_mask;
  h = sh->prop_hash_end[-h1 - 1];
  prop = get_shape_prop(sh);
  lpr = NULL;
  lpr_idx = 0; /* prevent warning */
  while (h != 0) {
    pr = &prop[h - 1];
    if (likely(pr->atom == atom)) {
      /* found ! */
      if (!(pr->flags & LEPUS_PROP_CONFIGURABLE)) return FALSE;
      /* realloc the shape if needed */
      if (lpr) lpr_idx = lpr - get_shape_prop(sh);
      if (js_shape_prepare_update(ctx, p, &pr)) return -1;
      sh = p->shape;
      /* remove property */
      if (lpr) {
        lpr = get_shape_prop(sh) + lpr_idx;
        lpr->hash_next = pr->hash_next;
      } else {
        sh->prop_hash_end[-h1 - 1] = pr->hash_next;
      }
      /* free the entry */
      pr1 = &p->prop[h - 1];
      free_property(ctx->rt, pr1, pr->flags);
      /* put default values */
      pr->flags = 0;
      pr->atom = JS_ATOM_NULL;
      pr1->u.value = LEPUS_UNDEFINED;
      return TRUE;
    }
    lpr = pr;
    h = pr->hash_next;
  }

  if (p->is_exotic) {
    if (p->fast_array) {
      uint32_t idx;
      if (JS_AtomIsArrayIndex(ctx, &idx, atom) && idx < p->u.array.count) {
        if (p->class_id == JS_CLASS_ARRAY ||
            p->class_id == JS_CLASS_ARGUMENTS) {
          /* Special case deleting the last element of a fast Array */
          if (idx == p->u.array.count - 1) {
            p->u.array.count = idx;
            return TRUE;
          }
          if (convert_fast_array_to_array(ctx, p)) return -1;
          goto redo;
        } else {
          return FALSE; /* not configurable */
        }
      }
    } else {
      const LEPUSClassExoticMethods *em =
          ctx->rt->class_array[p->class_id].exotic;
      if (em && em->delete_property) {
        return em->delete_property(ctx, LEPUS_MKPTR(LEPUS_TAG_OBJECT, p), atom);
      }
    }
  }
  /* not found */
  return TRUE;
}

static int call_setter(LEPUSContext *ctx, LEPUSObject *setter,
                       LEPUSValueConst this_obj, LEPUSValue val, int flags) {
  LEPUSValue ret = LEPUS_UNDEFINED, func = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &ret, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&func, HANDLE_TYPE_LEPUS_VALUE);
  if (likely(setter)) {
    func = LEPUS_MKPTR(LEPUS_TAG_OBJECT, setter);
    /* Note: the field could be removed in the setter */
    ret = JS_CallFree_GC(ctx, func, this_obj, 1,
                         reinterpret_cast<LEPUSValueConst *>(&val));
    if (LEPUS_IsException(ret)) return -1;
    return TRUE;
  } else {
    if ((flags & LEPUS_PROP_THROW) ||
        ((flags & LEPUS_PROP_THROW_STRICT) && is_strict_mode(ctx))) {
      LEPUS_ThrowTypeError(ctx, "no setter for property");
      return -1;
    }
    return FALSE;
  }
}

/* set the array length and remove the array elements if necessary. */
int set_array_length_gc(LEPUSContext *ctx, LEPUSObject *p, LEPUSValue val,
                        int flags) {
  uint32_t len, idx, cur_len;
  int i, ret;

  ret = JS_ToArrayLengthFree(ctx, &len, val, FALSE);
  if (ret) return -1;
  if (likely(p->fast_array)) {
    uint32_t old_len = p->u.array.count;
    if (len < old_len) {
      p->u.array.count = len;
    }
    p->prop[0].u.value = JS_NewUint32(ctx, len);
  } else {
    /* Note: length is always a uint32 because the object is an
       array */
    JS_ToInt32_GC(ctx, reinterpret_cast<int32_t *>(&cur_len),
                  p->prop[0].u.value);
    if (len < cur_len) {
      uint32_t d;
      JSShape *sh;
      JSShapeProperty *pr;

      d = cur_len - len;
      sh = p->shape;
      if (d <= sh->prop_count) {
        JSAtom atom;
        HandleScope block_scope(ctx->rt);

        /* faster to iterate */
        while (cur_len > len) {
          atom = JS_NewAtomUInt32_GC(ctx, cur_len - 1);
          block_scope.PushLEPUSAtom(atom);
          ret = delete_property(ctx, p, atom);
          if (unlikely(!ret)) {
            /* unlikely case: property is not
               configurable */
            break;
          }
          cur_len--;
        }
      } else {
        /* faster to iterate thru all the properties. Need two
           passes in case one of the property is not
           configurable */
        cur_len = len;
        for (i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) {
          if (pr->atom != JS_ATOM_NULL &&
              JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) {
            if (idx >= cur_len && !(pr->flags & LEPUS_PROP_CONFIGURABLE)) {
              cur_len = idx + 1;
            }
          }
        }

        for (i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) {
          if (pr->atom != JS_ATOM_NULL &&
              JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) {
            if (idx >= cur_len) {
              /* remove the property */
              delete_property(ctx, p, pr->atom);
              /* WARNING: the shape may have been modified */
              sh = p->shape;
              pr = get_shape_prop(sh) + i;
            }
          }
        }
      }
    } else {
      cur_len = len;
    }
    set_value_gc(ctx, &p->prop[0].u.value, JS_NewUint32(ctx, cur_len));
    if (unlikely(cur_len > len)) {
      return JS_ThrowTypeErrorOrFalse(ctx, flags, "not configurable");
    }
  }
  return TRUE;
}

/* Preconditions: 'p' must be of class JS_CLASS_ARRAY, p->fast_array =
   TRUE and p->extensible = TRUE */
static int add_fast_array_element(LEPUSContext *ctx, LEPUSObject *p,
                                  LEPUSValue val, int flags) {
  uint32_t new_len, array_len;
  /* extend the array by one */
  /* XXX: convert to slow array if new_len > 2^31-1 elements */
  new_len = p->u.array.count + 1;
  /* update the length if necessary. We assume that if the length is
     not an integer, then if it >= 2^31.  */
  if (likely(LEPUS_VALUE_IS_INT(p->prop[0].u.value))) {
    array_len = LEPUS_VALUE_GET_INT(p->prop[0].u.value);
    if (new_len > array_len) {
      if (unlikely(!(get_shape_prop(p->shape)->flags & LEPUS_PROP_WRITABLE))) {
        return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length);
      }
      p->prop[0].u.value = LEPUS_NewInt32(ctx, new_len);
    }
  }
  if (unlikely(new_len > p->u.array.u1.size)) {
    uint32_t new_size;
    size_t slack;
    LEPUSValue *new_array_prop;
    /* XXX: potential arithmetic overflow */
    new_size = max_int(new_len, p->u.array.u1.size * 3 / 2);
    new_array_prop = static_cast<LEPUSValue *>(
        lepus_realloc2(ctx, p->u.array.u.values, sizeof(LEPUSValue) * new_size,
                       &slack, ALLOC_TAG_WITHOUT_PTR));
    if (!new_array_prop) {
      return -1;
    }
    new_size += slack / sizeof(*new_array_prop);
    p->u.array.u.values = new_array_prop;
    p->u.array.u1.size = new_size;
  }
  p->u.array.u.values[new_len - 1] = val;
  p->u.array.count = new_len;
  return TRUE;
}

/* generic (and slower) version of LEPUS_SetProperty() for Reflect.set() */
int JS_SetPropertyGeneric_GC(LEPUSContext *ctx, LEPUSObject *p, JSAtom prop,
                             LEPUSValue val, LEPUSValueConst this_obj,
                             int flags) {
  int ret;
  LEPUSPropertyDescriptor desc;
  while (p != NULL) {
    if (p->is_exotic) {
      const LEPUSClassExoticMethods *em =
          ctx->rt->class_array[p->class_id].exotic;
      if (em && em->set_property) {
        ret = em->set_property(ctx, LEPUS_MKPTR(LEPUS_TAG_OBJECT, p), prop, val,
                               this_obj, flags);
        return ret;
      }
    }

    ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop);
    if (ret < 0) return ret;
    if (ret) {
      if (desc.flags & LEPUS_PROP_GETSET) {
        LEPUSObject *setter;
        if (LEPUS_IsUndefined(desc.setter))
          setter = NULL;
        else
          setter = LEPUS_VALUE_GET_OBJ(desc.setter);
        ret = call_setter(ctx, setter, this_obj, val, flags);
        return ret;
      } else {
        if (!(desc.flags & LEPUS_PROP_WRITABLE)) {
          goto read_only_error;
        }
      }
      break;
    }
    p = p->shape->proto;
  }
  this_obj = JSRef2Value(ctx, this_obj);
  if (!LEPUS_IsObject(this_obj))
    return JS_ThrowTypeErrorOrFalse(ctx, flags, "receiver is not an object");

  p = LEPUS_VALUE_GET_OBJ(this_obj);

  /* modify the property in this_obj if it already exists */
  ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop);
  if (ret < 0) return ret;
  if (ret) {
    if (desc.flags & LEPUS_PROP_GETSET) {
      return JS_ThrowTypeErrorOrFalse(ctx, flags, "setter is forbidden");
    } else {
      if (!(desc.flags & LEPUS_PROP_WRITABLE) ||
          p->class_id == JS_CLASS_MODULE_NS) {
      read_only_error:
        return JS_ThrowTypeErrorReadOnly(ctx, flags, prop);
      }
    }
    ret = JS_DefineProperty_GC(ctx, this_obj, prop, val, LEPUS_UNDEFINED,
                               LEPUS_UNDEFINED, LEPUS_PROP_HAS_VALUE);
    return ret;
  } else if (p->class_id == JS_CLASS_BIG_INT64_ARRAY ||
             p->class_id == JS_CLASS_BIG_UINT64_ARRAY) {
    ret = JS_AtomIsNumericIndex(ctx, prop);
    if (ret != 0) {
      if (ret < 0) {
        return -1;
      }
      int64_t v;
      if (JS_ToBigInt64Free(ctx, &v, val)) return -1;
      return TRUE;
    }
  }

  ret = JS_CreateProperty(ctx, p, prop, val, LEPUS_UNDEFINED, LEPUS_UNDEFINED,
                          flags | LEPUS_PROP_HAS_VALUE |
                              LEPUS_PROP_HAS_ENUMERABLE |
                              LEPUS_PROP_HAS_WRITABLE |
                              LEPUS_PROP_HAS_CONFIGURABLE | LEPUS_PROP_C_W_E);
  return ret;
}

int JS_SetPropertyInternal_GC(LEPUSContext *ctx, LEPUSValueConst this_obj,
                              JSAtom prop, LEPUSValue val, int flags) {
  return JS_SetPropertyInternalImpl_GC(ctx, this_obj, prop, val, flags);
}

/* return -1 in case of exception or TRUE or FALSE. Warning: 'val' is
   freed by the function. 'flags' is a bitmask of LEPUS_PROP_NO_ADD,
   LEPUS_PROP_THROW or LEPUS_PROP_THROW_STRICT. If LEPUS_PROP_NO_ADD is set,
   the new property is not added and an error is raised. */

int JS_SetPropertyInternalImpl_GC(LEPUSContext *ctx, LEPUSValueConst this_obj,
                                  JSAtom prop, LEPUSValue val, int flags) {
  LEPUSObject *p, *p1;
  JSShapeProperty *prs;
  JSProperty *pr;
  int64_t tag;
  int ret;
  char buf[ATOM_GET_STR_BUF_SIZE];
  intptr_t offset = 0;

  tag = LEPUS_VALUE_GET_TAG(this_obj);
#ifdef ENABLE_LEPUSNG
  // <Primjs begin>
  if (tag == LEPUS_TAG_LEPUS_REF) {
    HandleScope block_scope(ctx->rt);
    LEPUSValue prop_str = LEPUS_UNDEFINED;
    int idx = -1;
    if (__JS_AtomIsTaggedInt(prop)) {
      idx = __JS_AtomToUInt32(prop);
    } else {
      prop_str = JS_AtomToString_GC(ctx, prop);
      block_scope.PushHandle(&prop_str, HANDLE_TYPE_LEPUS_VALUE);
    }
    LEPUSValue ret =
        ctx->rt->js_callbacks_.set_property(ctx, this_obj, prop_str, idx, val);
    if (LEPUS_IsException(ret)) {
      return -1;
    }
    if (LEPUS_IsUndefined(ret)) {
      auto cache = static_cast<LEPUSLepusRef *>(LEPUS_VALUE_GET_PTR(this_obj))
                       ->lepus_val;
      if (LEPUS_VALUE_IS_OBJECT(cache))
        JS_SetPropertyInternalImpl_GC(ctx, cache, prop, val, flags);
      return TRUE;
    }
    // trace_gc, todo; global handles push this_obj
    this_obj = ret;
    tag = LEPUS_VALUE_GET_TAG(this_obj);
  }
// <Primjs end>
#endif
  if (unlikely(tag != LEPUS_TAG_OBJECT)) {
    switch (tag) {
      case LEPUS_TAG_NULL:
        // if not in the strict mode, set properties of null will do nothing and
        // will not throw exception
        if (ctx->no_lepus_strict_mode) {
          return 0;
        } else {
          JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of null", prop);
          return -1;
        }
      case LEPUS_TAG_UNDEFINED:
        if (ctx->no_lepus_strict_mode) {
          return 0;
        } else {
          JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of undefined",
                                prop);
          return -1;
        }
// <Primjs begin>
#ifdef ENABLE_LEPUSNG
      case LEPUS_TAG_BIG_INT:
        p = NULL;
        p1 = NULL;
        goto prototype_lookup;
#endif
        // <Primjs end>
      default:
        /* even on a primitive type we can have setters on the prototype */
        p = NULL;
        p1 = LEPUS_VALUE_GET_OBJ(JS_GetPrototype_GC(ctx, this_obj));
        goto prototype_lookup;
    }
  }
  p = LEPUS_VALUE_GET_OBJ(this_obj);

  CheckObjectCtx(ctx, val);

retry:
  prs = find_own_property(&pr, p, prop);
  if (prs) {
    if (likely((prs->flags & (LEPUS_PROP_TMASK | LEPUS_PROP_WRITABLE |
                              LEPUS_PROP_LENGTH)) == LEPUS_PROP_WRITABLE)) {
      /* fast case */
      set_value_gc(ctx, &pr->u.value, val);
      return TRUE;
    } else if ((prs->flags & (LEPUS_PROP_LENGTH | LEPUS_PROP_WRITABLE)) ==
               (LEPUS_PROP_LENGTH | LEPUS_PROP_WRITABLE)) {
      assert(p->class_id == JS_CLASS_ARRAY);
      assert(prop == JS_ATOM_length);
      return set_array_length_gc(ctx, p, val, flags);
    } else if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_GETSET) {
      return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags);
    } else if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_VARREF) {
      /* LEPUS_PROP_WRITABLE is always true for variable
         references, but they are write protected in module name
         spaces. */
      if (p->class_id == JS_CLASS_MODULE_NS) goto read_only_prop;
      set_value_gc(ctx, pr->u.var_ref->pvalue, val);
      return TRUE;
    } else if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_AUTOINIT) {
      /* Instantiate property and retry (potentially useless) */
      if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) {
        return -1;
      }
      goto retry;
    } else {
      goto read_only_prop;
    }
  }

  p1 = p;
  for (;;) {
    if (p1->is_exotic) {
      if (p1->fast_array) {
        if (__JS_AtomIsTaggedInt(prop)) {
          uint32_t idx = __JS_AtomToUInt32(prop);
          if (idx < p1->u.array.count) {
            if (unlikely(p == p1))
              return JS_SetPropertyValue_GC(
                  ctx, this_obj, LEPUS_NewInt32(ctx, idx), val, flags);
            else
              break;
          } else if (p1->class_id >= JS_CLASS_UINT8C_ARRAY &&
                     p1->class_id <= JS_CLASS_BIG_UINT64_ARRAY) {
            goto typed_array_oob;
          }
        } else if (p1->class_id >= JS_CLASS_UINT8C_ARRAY &&
                   p1->class_id <= JS_CLASS_BIG_UINT64_ARRAY) {
          ret = JS_AtomIsNumericIndex(ctx, prop);
          if (ret != 0) {
            if (ret < 0) {
              return -1;
            }
          typed_array_oob:
            if (p == p1) {
              if (p1->class_id == JS_CLASS_BIG_UINT64_ARRAY ||
                  p1->class_id == JS_CLASS_BIG_INT64_ARRAY) {
                int64_t v;
                if (JS_ToBigInt64Free(ctx, &v, val)) return -1;
              } else {
                val = JS_ToNumberFree(ctx, val);
                if (LEPUS_IsException(val)) return -1;
              }
            }
            return TRUE;
          }
        }
      } else {
        const LEPUSClassExoticMethods *em =
            ctx->rt->class_array[p1->class_id].exotic;
        if (em) {
          if (em->set_property) {
            ret = em->set_property(ctx, LEPUS_MKPTR(LEPUS_TAG_OBJECT, p1), prop,
                                   val, this_obj, flags);
            return ret;
          }
          if (em->get_own_property) {
            LEPUSPropertyDescriptor desc;
            ret = em->get_own_property(ctx, &desc,
                                       LEPUS_MKPTR(LEPUS_TAG_OBJECT, p1), prop);
            if (ret < 0) {
              return ret;
            }
            if (ret) {
              if (desc.flags & LEPUS_PROP_GETSET) {
                LEPUSObject *setter;
                if (LEPUS_IsUndefined(desc.setter))
                  setter = NULL;
                else
                  setter = LEPUS_VALUE_GET_OBJ(desc.setter);
                ret = call_setter(ctx, setter, this_obj, val, flags);
                return ret;
              } else {
                if (!(desc.flags & LEPUS_PROP_WRITABLE)) goto read_only_prop;
                if (likely(p == p1)) {
                  ret = JS_DefineProperty_GC(ctx, this_obj, prop, val,
                                             LEPUS_UNDEFINED, LEPUS_UNDEFINED,
                                             LEPUS_PROP_HAS_VALUE);
                  return ret;
                } else {
                  break;
                }
              }
            }
          }
        }
      }
    }
    p1 = p1->shape->proto;
  prototype_lookup:
    if (!p1) break;

  retry2:
    prs = find_own_property(&pr, p1, prop);
    if (prs) {
      if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_GETSET) {
        return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags);
      } else if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_AUTOINIT) {
        /* Instantiate property and retry (potentially useless) */
        if (JS_AutoInitProperty(ctx, p1, prop, pr, prs)) return -1;
        goto retry2;
      } else if (!(prs->flags & LEPUS_PROP_WRITABLE)) {
      read_only_prop:
        return JS_ThrowTypeErrorReadOnly(ctx, flags, prop);
      }
    }
  }

  if (unlikely(flags & LEPUS_PROP_NO_ADD)) {
    JS_ThrowReferenceErrorNotDefined_GC(ctx, prop);
    return -1;
  }

  if (unlikely(!p)) {
    return JS_ThrowTypeErrorOrFalse(ctx, flags, "not an object");
  }

  if (unlikely(!p->extensible)) {
    return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible");
  }

  if (p->is_exotic) {
    if (p->class_id == JS_CLASS_ARRAY && p->fast_array &&
        __JS_AtomIsTaggedInt(prop)) {
      uint32_t idx = __JS_AtomToUInt32(prop);
      if (idx == p->u.array.count) {
        /* fast case */
        return add_fast_array_element(ctx, p, val, flags);
      } else {
        goto generic_create_prop;
      }
    } else {
    generic_create_prop:
      ret = JS_CreateProperty(
          ctx, p, prop, val, LEPUS_UNDEFINED, LEPUS_UNDEFINED,
          flags | LEPUS_PROP_HAS_VALUE | LEPUS_PROP_HAS_ENUMERABLE |
              LEPUS_PROP_HAS_WRITABLE | LEPUS_PROP_HAS_CONFIGURABLE |
              LEPUS_PROP_C_W_E);
      return ret;
    }
  }
  pr = add_property_gc(ctx, p, prop, LEPUS_PROP_C_W_E);
  if (unlikely(!pr)) {
    return -1;
  }
  pr->u.value = val;
  return TRUE;
}

/* flags can be LEPUS_PROP_THROW or LEPUS_PROP_THROW_STRICT */
int JS_SetPropertyValue_GC(LEPUSContext *ctx, LEPUSValueConst this_obj,
                           LEPUSValue prop, LEPUSValue val, int flags) {
  if (likely(LEPUS_VALUE_IS_OBJECT(this_obj) && LEPUS_VALUE_IS_INT(prop))) {
    LEPUSObject *p;
    uint32_t idx;
    double d;
    int32_t v;

    /* fast path for array access */
    p = LEPUS_VALUE_GET_OBJ(this_obj);
    idx = LEPUS_VALUE_GET_INT(prop);
    switch (p->class_id) {
      case JS_CLASS_ARRAY:
        if (unlikely(idx >= (uint32_t)p->u.array.count)) {
          LEPUSObject *p1;
          JSShape *sh1;

          /* fast path to add an element to the array */
          if (idx != (uint32_t)p->u.array.count || !p->fast_array ||
              !p->extensible)
            goto slow_path;
          /* check if prototype chain has a numeric property */
          p1 = p->shape->proto;
          while (p1 != NULL) {
            sh1 = p1->shape;
            if (p1->class_id == JS_CLASS_ARRAY) {
              if (unlikely(!p1->fast_array)) goto slow_path;
            } else if (p1->class_id == JS_CLASS_OBJECT) {
              if (unlikely(sh1->has_small_array_index)) goto slow_path;
            } else {
              goto slow_path;
            }
            p1 = sh1->proto;
          }
          /* add element */
          return add_fast_array_element(ctx, p, val, flags);
        }
        set_value_gc(ctx, &p->u.array.u.values[idx], val);
        break;
      case JS_CLASS_ARGUMENTS:
        if (unlikely(idx >= (uint32_t)p->u.array.count)) goto slow_path;
        set_value_gc(ctx, &p->u.array.u.values[idx], val);
        break;
      case JS_CLASS_UINT8C_ARRAY:
        if (JS_ToUint8ClampFree(ctx, &v, val)) return -1;
        /* Note: the conversion can detach the typed array, so the
           array bound check must be done after */
        if (unlikely(idx >= (uint32_t)p->u.array.count)) goto ta_out_of_bound;
        p->u.array.u.uint8_ptr[idx] = v;
        break;
      case JS_CLASS_INT8_ARRAY:
      case JS_CLASS_UINT8_ARRAY:
        if (JS_ToInt32Free(ctx, &v, val)) return -1;
        if (unlikely(idx >= (uint32_t)p->u.array.count)) goto ta_out_of_bound;
        p->u.array.u.uint8_ptr[idx] = v;
        break;
      case JS_CLASS_INT16_ARRAY:
      case JS_CLASS_UINT16_ARRAY:
        if (JS_ToInt32Free(ctx, &v, val)) return -1;
        if (unlikely(idx >= (uint32_t)p->u.array.count)) goto ta_out_of_bound;
        p->u.array.u.uint16_ptr[idx] = v;
        break;
      case JS_CLASS_INT32_ARRAY:
      case JS_CLASS_UINT32_ARRAY:
        if (JS_ToInt32Free(ctx, &v, val)) return -1;
        if (unlikely(idx >= (uint32_t)p->u.array.count)) goto ta_out_of_bound;
        p->u.array.u.uint32_ptr[idx] = v;
        break;
      case JS_CLASS_BIG_INT64_ARRAY:
      case JS_CLASS_BIG_UINT64_ARRAY:
        /* XXX: need specific conversion function */
        {
          int64_t v;
          if (JS_ToBigInt64Free(ctx, &v, val)) return -1;
          if (unlikely(idx >= (uint32_t)p->u.array.count)) goto ta_out_of_bound;
          p->u.array.u.uint64_ptr[idx] = v;
        }
        break;
      case JS_CLASS_FLOAT32_ARRAY:
        if (JS_ToFloat64Free(ctx, &d, val)) return -1;
        if (unlikely(idx >= (uint32_t)p->u.array.count)) goto ta_out_of_bound;
        p->u.array.u.float_ptr[idx] = d;
        break;
      case JS_CLASS_FLOAT64_ARRAY:
        if (JS_ToFloat64Free(ctx, &d, val)) return -1;
        if (unlikely(idx >= (uint32_t)p->u.array.count)) {
        ta_out_of_bound:
          return JS_ThrowTypeErrorOrFalse(ctx, flags,
                                          "out-of-bound numeric index");
          /* Accroding to 10.4.5.11, if index is invalid, return
           * NormalCompletion(undefined) */
        }
        p->u.array.u.double_ptr[idx] = d;
        break;
      default:
        goto slow_path;
    }
    return TRUE;
  } else {
    JSAtom atom;
    int ret;
  slow_path:
    atom = js_value_to_atom_gc(ctx, prop);
    if (unlikely(atom == JS_ATOM_NULL)) {
      return -1;
    }
    HandleScope block_scope(ctx->rt);
    block_scope.PushLEPUSAtom(atom);
    ret = JS_SetPropertyInternal_GC(ctx, this_obj, atom, val, flags);
    return ret;
  }
}

int JS_SetPropertyUint32_GC(LEPUSContext *ctx, LEPUSValueConst this_obj,
                            uint32_t idx, LEPUSValue val) {
  return JS_SetPropertyValue_GC(ctx, this_obj, JS_NewUint32(ctx, idx), val,
                                LEPUS_PROP_THROW);
}

int JS_SetPropertyInt64_GC(LEPUSContext *ctx, LEPUSValueConst this_obj,
                           int64_t idx, LEPUSValue val) {
  JSAtom prop;
  int res;

  if ((uint64_t)idx <= INT32_MAX) {
    /* fast path for fast arrays */
    return JS_SetPropertyValue_GC(ctx, this_obj, LEPUS_NewInt32(ctx, idx), val,
                                  LEPUS_PROP_THROW);
  }
  prop = JS_NewAtomInt64(ctx, idx);
  if (prop == JS_ATOM_NULL) {
    return -1;
  }
  HandleScope func_scope(ctx->rt);
  func_scope.PushLEPUSAtom(prop);
  res = JS_SetPropertyInternal_GC(ctx, this_obj, prop, val, LEPUS_PROP_THROW);
  return res;
}

int JS_SetPropertyStr_GC(LEPUSContext *ctx, LEPUSValueConst this_obj,
                         const char *prop, LEPUSValue val) {
  JSAtom atom;
  int ret;
  atom = LEPUS_NewAtom(ctx, prop);
  HandleScope func_scope(ctx->rt);
  func_scope.PushLEPUSAtom(atom);
  ret = JS_SetPropertyInternal_GC(ctx, this_obj, atom, val, LEPUS_PROP_THROW);
  return ret;
}

/* compute the property flags. For each flag: (JS_PROP_HAS_x forces
   it, otherwise def_flags is used)
   Note: makes assumption about the bit pattern of the flags
*/
static int get_prop_flags(int flags, int def_flags) {
  int mask;
  mask = (flags >> LEPUS_PROP_HAS_SHIFT) & LEPUS_PROP_C_W_E;
  return (flags & mask) | (def_flags & ~mask);
}

static int JS_CreateProperty(LEPUSContext *ctx, LEPUSObject *p, JSAtom prop,
                             LEPUSValueConst val, LEPUSValueConst getter,
                             LEPUSValueConst setter, int flags) {
  JSProperty *pr;
  int ret, prop_flags;

  /* add a new property or modify an existing exotic one */
  if (p->is_exotic) {
    if (p->class_id == JS_CLASS_ARRAY) {
      uint32_t idx, len;

      if (p->fast_array) {
        if (__JS_AtomIsTaggedInt(prop)) {
          idx = __JS_AtomToUInt32(prop);
          if (idx == p->u.array.count) {
            if (!p->extensible) goto not_extensible;
            if (flags & (LEPUS_PROP_HAS_GET | LEPUS_PROP_HAS_SET))
              goto convert_to_array;
            prop_flags = get_prop_flags(flags, 0);
            if (prop_flags != LEPUS_PROP_C_W_E) goto convert_to_array;
            return add_fast_array_element(ctx, p, val, flags);
          } else {
            goto convert_to_array;
          }
        } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) {
          /* convert the fast array to normal array */
        convert_to_array:
          if (convert_fast_array_to_array(ctx, p)) return -1;
          goto generic_array;
        }
      } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) {
        JSProperty *plen;
        JSShapeProperty *pslen;
      generic_array:
        /* update the length field */
        plen = &p->prop[0];
        JS_ToInt32_GC(ctx, reinterpret_cast<int32_t *>(&len), plen->u.value);
        if ((idx + 1) > len) {
          pslen = get_shape_prop(p->shape);
          if (unlikely(!(pslen->flags & LEPUS_PROP_WRITABLE)))
            return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length);
          /* XXX: should update the length after defining
             the property */
          len = idx + 1;
          set_value_gc(ctx, &plen->u.value, JS_NewUint32(ctx, len));
        }
      }
    } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
               p->class_id <= JS_CLASS_BIG_UINT64_ARRAY) {
      ret = JS_AtomIsNumericIndex(ctx, prop);
      if (ret != 0) {
        if (ret < 0) return -1;
        /* 10.4.5.5: Always return true */
        // return JS_ThrowTypeErrorOrFalse(
        //     ctx, flags, "cannot create numeric index in typed array");
      }
    } else if (!(flags & LEPUS_PROP_NO_EXOTIC)) {
      const LEPUSClassExoticMethods *em =
          ctx->rt->class_array[p->class_id].exotic;
      if (em) {
        if (em->define_own_property) {
          return em->define_own_property(ctx, LEPUS_MKPTR(LEPUS_TAG_OBJECT, p),
                                         prop, val, getter, setter, flags);
        }
        ret = JS_IsExtensible_GC(ctx, LEPUS_MKPTR(LEPUS_TAG_OBJECT, p));
        if (ret < 0) return -1;
        if (!ret) goto not_extensible;
      }
    }
  }

  if (!p->extensible) {
  not_extensible:
    return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible");
  }

  if (flags & (LEPUS_PROP_HAS_GET | LEPUS_PROP_HAS_SET)) {
    prop_flags = (flags & (LEPUS_PROP_CONFIGURABLE | LEPUS_PROP_ENUMERABLE)) |
                 LEPUS_PROP_GETSET;
  } else {
    prop_flags = flags & LEPUS_PROP_C_W_E;
  }
  pr = add_property_gc(ctx, p, prop, prop_flags);
  if (unlikely(!pr)) return -1;
  if (flags & (LEPUS_PROP_HAS_GET | LEPUS_PROP_HAS_SET)) {
    pr->u.getset.getter = NULL;
    if ((flags & LEPUS_PROP_HAS_GET) && LEPUS_IsFunction(ctx, getter)) {
      pr->u.getset.getter = LEPUS_VALUE_GET_OBJ(getter);
    }
    pr->u.getset.setter = NULL;
    if ((flags & LEPUS_PROP_HAS_SET) && LEPUS_IsFunction(ctx, setter)) {
      pr->u.getset.setter = LEPUS_VALUE_GET_OBJ(setter);
    }
  } else {
    if (flags & LEPUS_PROP_HAS_VALUE) {
      pr->u.value = val;
    } else {
      pr->u.value = LEPUS_UNDEFINED;
    }
  }
  return TRUE;
}

/* return FALSE if not OK */
static BOOL check_define_prop_flags(int prop_flags, int flags) {
  BOOL has_accessor, is_getset;

  if (!(prop_flags & LEPUS_PROP_CONFIGURABLE)) {
    if ((flags & (LEPUS_PROP_HAS_CONFIGURABLE | LEPUS_PROP_CONFIGURABLE)) ==
        (LEPUS_PROP_HAS_CONFIGURABLE | LEPUS_PROP_CONFIGURABLE)) {
      return FALSE;
    }
    if ((flags & LEPUS_PROP_HAS_ENUMERABLE) &&
        (flags & LEPUS_PROP_ENUMERABLE) != (prop_flags & LEPUS_PROP_ENUMERABLE))
      return FALSE;
  }
  if (flags & (LEPUS_PROP_HAS_VALUE | LEPUS_PROP_HAS_WRITABLE |
               LEPUS_PROP_HAS_GET | LEPUS_PROP_HAS_SET)) {
    if (!(prop_flags & LEPUS_PROP_CONFIGURABLE)) {
      has_accessor = ((flags & (LEPUS_PROP_HAS_GET | LEPUS_PROP_HAS_SET)) != 0);
      is_getset = ((prop_flags & LEPUS_PROP_TMASK) == LEPUS_PROP_GETSET);
      if (has_accessor != is_getset) return FALSE;
      if (!has_accessor && !is_getset && !(prop_flags & LEPUS_PROP_WRITABLE)) {
        /* not writable: cannot set the writable bit */
        if ((flags & (LEPUS_PROP_HAS_WRITABLE | LEPUS_PROP_WRITABLE)) ==
            (LEPUS_PROP_HAS_WRITABLE | LEPUS_PROP_WRITABLE))
          return FALSE;
      }
    }
  }
  return TRUE;
}

/* ensure that the shape can be safely modified */
QJS_STATIC int js_shape_prepare_update(LEPUSContext *ctx, LEPUSObject *p,
                                       JSShapeProperty **pprs) {
  JSShape *sh;
  uint32_t idx = 0; /* prevent warning */

  sh = p->shape;
  if (sh->is_hashed) {
    if (sh->header.ref_count != 1) {
      if (pprs) idx = *pprs - get_shape_prop(sh);
      /* clone the shape (the resulting one is no longer hashed) */
      sh = js_clone_shape(ctx, sh);
      if (!sh) return -1;
      js_free_shape(ctx->rt, p->shape);
      p->shape = sh;
      if (pprs) *pprs = get_shape_prop(sh) + idx;
    } else {
      js_shape_hash_unlink(ctx->rt, sh);
      sh->is_hashed = FALSE;
    }
  }
  return 0;
}

static int js_update_property_flags(LEPUSContext *ctx, LEPUSObject *p,
                                    JSShapeProperty **pprs, int flags) {
  if (flags != (*pprs)->flags) {
    if (js_shape_prepare_update(ctx, p, pprs)) return -1;
    (*pprs)->flags = flags;
  }
  return 0;
}

/* allowed flags:
   LEPUS_PROP_CONFIGURABLE, LEPUS_PROP_WRITABLE, LEPUS_PROP_ENUMERABLE
   LEPUS_PROP_HAS_GET, LEPUS_PROP_HAS_SET, LEPUS_PROP_HAS_VALUE,
   LEPUS_PROP_HAS_CONFIGURABLE, LEPUS_PROP_HAS_WRITABLE,
   LEPUS_PROP_HAS_ENUMERABLE, LEPUS_PROP_THROW, LEPUS_PROP_NO_EXOTIC. If
   LEPUS_PROP_THROW is set, return an exception instead of FALSE. if
   LEPUS_PROP_NO_EXOTIC is set, do not call the exotic define_own_property
   callback. return -1 (exception), FALSE or TRUE.
*/
int JS_DefineProperty_GC(LEPUSContext *ctx, LEPUSValueConst this_obj,
                         JSAtom prop, LEPUSValueConst val,
                         LEPUSValueConst getter, LEPUSValueConst setter,
                         int flags) {
  LEPUSObject *p;
  JSShapeProperty *prs;
  JSProperty *pr;
  int mask, res;
  // <Primjs begin>
  this_obj = JSRef2Value(ctx, this_obj);
  // <Primjs end>
  if (LEPUS_VALUE_IS_NOT_OBJECT(this_obj)) {
    JS_ThrowTypeErrorNotAnObject(ctx);
    return -1;
  }
  p = LEPUS_VALUE_GET_OBJ(this_obj);

redo_prop_update:
  prs = find_own_property(&pr, p, prop);
  if (prs) {
    /* the range of the Array length property is always tested before */
    if ((prs->flags & LEPUS_PROP_LENGTH) && (flags & LEPUS_PROP_HAS_VALUE)) {
      uint32_t array_length;
      if (JS_ToArrayLengthFree(ctx, &array_length, val, FALSE)) {
        return -1;
      }
      /* this code relies on the fact that Uint32 are never allocated */
      val = (LEPUSValueConst)JS_NewUint32(ctx, array_length);
      /* prs may have been modified */
      prs = find_own_property(&pr, p, prop);
      assert(prs != NULL);
    }
    /* property already exists */
    if (!check_define_prop_flags(prs->flags, flags)) {
    not_configurable:
      return JS_ThrowTypeErrorOrFalse(ctx, flags,
                                      "property is not configurable");
    }
    if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_AUTOINIT) {
      /* Instantiate property and retry */
      if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) return -1;
      goto redo_prop_update;
    }

    if (flags & (LEPUS_PROP_HAS_VALUE | LEPUS_PROP_HAS_WRITABLE |
                 LEPUS_PROP_HAS_GET | LEPUS_PROP_HAS_SET)) {
      if (flags & (LEPUS_PROP_HAS_GET | LEPUS_PROP_HAS_SET)) {
        LEPUSObject *new_getter, *new_setter;

        if (LEPUS_IsFunction(ctx, getter)) {
          new_getter = LEPUS_VALUE_GET_OBJ(getter);
        } else {
          new_getter = NULL;
        }
        if (LEPUS_IsFunction(ctx, setter)) {
          new_setter = LEPUS_VALUE_GET_OBJ(setter);
        } else {
          new_setter = NULL;
        }

        if ((prs->flags & LEPUS_PROP_TMASK) != LEPUS_PROP_GETSET) {
          if (js_shape_prepare_update(ctx, p, &prs)) return -1;
          /* convert to getset */
          prs->flags =
              (prs->flags & (LEPUS_PROP_CONFIGURABLE | LEPUS_PROP_ENUMERABLE)) |
              LEPUS_PROP_GETSET;
          pr->u.getset.getter = NULL;
          pr->u.getset.setter = NULL;
        } else {
          if (!(prs->flags & LEPUS_PROP_CONFIGURABLE)) {
            if ((flags & LEPUS_PROP_HAS_GET) &&
                new_getter != pr->u.getset.getter) {
              goto not_configurable;
            }
            if ((flags & LEPUS_PROP_HAS_SET) &&
                new_setter != pr->u.getset.setter) {
              goto not_configurable;
            }
          }
        }
        if (flags & LEPUS_PROP_HAS_GET) {
          pr->u.getset.getter = new_getter;
        }
        if (flags & LEPUS_PROP_HAS_SET) {
          pr->u.getset.setter = new_setter;
        }
      } else {
        if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_GETSET) {
          /* convert to data descriptor */
          if (js_shape_prepare_update(ctx, p, &prs)) return -1;
          prs->flags &= ~(LEPUS_PROP_TMASK | LEPUS_PROP_WRITABLE);
          pr->u.value = LEPUS_UNDEFINED;
        } else if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_VARREF) {
          /* Note: LEPUS_PROP_VARREF is always writable */
        } else {
          if ((prs->flags & (LEPUS_PROP_CONFIGURABLE | LEPUS_PROP_WRITABLE)) ==
                  0 &&
              (flags & LEPUS_PROP_HAS_VALUE)) {
            if (!js_same_value(ctx, val, pr->u.value)) {
              goto not_configurable;
            } else {
              return TRUE;
            }
          }
        }
        if (prs->flags & LEPUS_PROP_LENGTH) {
          if (flags & LEPUS_PROP_HAS_VALUE) {
            res = set_array_length_gc(ctx, p, val, flags);
          } else {
            res = TRUE;
          }
          /* still need to reset the writable flag if needed.
             The LEPUS_PROP_LENGTH is reset to have the correct
             read-only behavior in LEPUS_SetProperty(). */
          if ((flags & (LEPUS_PROP_HAS_WRITABLE | LEPUS_PROP_WRITABLE)) ==
              LEPUS_PROP_HAS_WRITABLE) {
            prs = get_shape_prop(p->shape);
            if (js_update_property_flags(
                    ctx, p, &prs,
                    prs->flags & ~(LEPUS_PROP_WRITABLE | LEPUS_PROP_LENGTH)))
              return -1;
          }
          return res;
        } else if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_VARREF) {
          if (flags & LEPUS_PROP_HAS_VALUE) {
            if (p->class_id == JS_CLASS_MODULE_NS) {
              /* LEPUS_PROP_WRITABLE is always true for variable
                 references, but they are write protected in module name
                 spaces. */
              if (!js_same_value(ctx, val, *pr->u.var_ref->pvalue))
                goto not_configurable;
            }
            /* update the reference */
            set_value_gc(ctx, pr->u.var_ref->pvalue, val);
          }
          /* if writable is set to false, no longer a
             reference (for mapped arguments) */
          if ((flags & (LEPUS_PROP_HAS_WRITABLE | LEPUS_PROP_WRITABLE)) ==
              LEPUS_PROP_HAS_WRITABLE) {
            LEPUSValue val1;
            if (js_shape_prepare_update(ctx, p, &prs)) return -1;
            val1 = *pr->u.var_ref->pvalue;
            pr->u.value = val1;
            prs->flags &= ~(LEPUS_PROP_TMASK | LEPUS_PROP_WRITABLE);
          }
        } else if ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_AUTOINIT) {
          /* XXX: should never happen, type was reset above */
          abort();
        } else {
          if (flags & LEPUS_PROP_HAS_VALUE) {
            pr->u.value = val;
          }
          if (flags & LEPUS_PROP_HAS_WRITABLE) {
            if (js_update_property_flags(ctx, p, &prs,
                                         (prs->flags & ~LEPUS_PROP_WRITABLE) |
                                             (flags & LEPUS_PROP_WRITABLE)))
              return -1;
          }
        }
      }
    }
    mask = 0;
    if (flags & LEPUS_PROP_HAS_CONFIGURABLE) mask |= LEPUS_PROP_CONFIGURABLE;
    if (flags & LEPUS_PROP_HAS_ENUMERABLE) mask |= LEPUS_PROP_ENUMERABLE;
    if (js_update_property_flags(ctx, p, &prs,
                                 (prs->flags & ~mask) | (flags & mask)))
      return -1;
    return TRUE;
  }

  /* handle modification of fast array elements */
  if (p->fast_array) {
    uint32_t idx;
    uint32_t prop_flags;
    if (p->class_id == JS_CLASS_ARRAY) {
      if (__JS_AtomIsTaggedInt(prop)) {
        idx = __JS_AtomToUInt32(prop);
        if (idx < p->u.array.count) {
          prop_flags = get_prop_flags(flags, LEPUS_PROP_C_W_E);
          if (prop_flags != LEPUS_PROP_C_W_E) goto convert_to_slow_array;
          if (flags & (LEPUS_PROP_HAS_GET | LEPUS_PROP_HAS_SET)) {
          convert_to_slow_array:
            if (convert_fast_array_to_array(ctx, p))
              return -1;
            else
              goto redo_prop_update;
          }
          if (flags & LEPUS_PROP_HAS_VALUE) {
            set_value_gc(ctx, &p->u.array.u.values[idx], val);
          }
          return TRUE;
        }
      }
    } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
               p->class_id <= JS_CLASS_BIG_UINT64_ARRAY) {
      HandleScope block_scope(ctx->rt);
      LEPUSValue num = LEPUS_UNDEFINED;
      block_scope.PushHandle(&num, HANDLE_TYPE_LEPUS_VALUE);
      int ret;

      if (!__JS_AtomIsTaggedInt(prop)) {
        /* slow path with to handle all numeric indexes */
        num = JS_AtomIsNumericIndex1(ctx, prop);
        if (LEPUS_IsUndefined(num)) goto typed_array_done;
        if (LEPUS_IsException(num)) return -1;
        ret = JS_NumberIsInteger(ctx, num);
        if (ret < 0) {
          return -1;
        }
        if (!ret) {
          return JS_ThrowTypeErrorOrFalse(ctx, flags,
                                          "non integer index in typed array");
        }
        ret = JS_NumberIsNegativeOrMinusZero(ctx, num);
        if (ret) {
          return JS_ThrowTypeErrorOrFalse(ctx, flags,
                                          "negative index in typed array");
        }
        if (!__JS_AtomIsTaggedInt(prop)) goto typed_array_oob;
      }
      idx = __JS_AtomToUInt32(prop);
      /* if the typed array is detached, p->u.array.count = 0 */
      if (idx >= p->u.array.count) {
      typed_array_oob:
        return JS_ThrowTypeErrorOrFalse(ctx, flags,
                                        "out-of-bound index in typed array");
      }
      prop_flags = get_prop_flags(flags, LEPUS_PROP_C_W_E);
      if (flags & (LEPUS_PROP_HAS_GET | LEPUS_PROP_HAS_SET) ||
          prop_flags != LEPUS_PROP_C_W_E) {
        return JS_ThrowTypeErrorOrFalse(ctx, flags, "invalid descriptor flags");
      }
      if (flags & LEPUS_PROP_HAS_VALUE) {
        return JS_SetPropertyValue_GC(ctx, this_obj, LEPUS_NewInt32(ctx, idx),
                                      val, flags);
      }
      return TRUE;
    typed_array_done: {}
    }
  }

  return JS_CreateProperty(ctx, p, prop, val, getter, setter, flags);
}

int JS_DefineAutoInitProperty_GC(
    LEPUSContext *ctx, LEPUSValueConst this_obj, JSAtom prop,
    LEPUSValue (*init_func)(LEPUSContext *ctx, LEPUSObject *obj, JSAtom prop,
                            void *opaque),
    void *opaque, int flags, bool need_find_prop) {
  LEPUSObject *p;
  JSProperty *pr;

  if (LEPUS_VALUE_IS_NOT_OBJECT(this_obj)) return FALSE;

  p = LEPUS_VALUE_GET_OBJ(this_obj);

  if (need_find_prop && find_own_property(&pr, p, prop)) {
    /* property already exists */
    abort();
    return FALSE;
  }

  /* Specialized CreateProperty */
  pr = add_property_gc(ctx, p, prop,
                       (flags & LEPUS_PROP_C_W_E) | LEPUS_PROP_AUTOINIT);
  if (unlikely(!pr)) return -1;
  pr->u.init.init_func = init_func;
  pr->u.init.opaque = opaque;
  return TRUE;
}

/* shortcut to add or redefine a new property value */
int JS_DefinePropertyValue_GC(LEPUSContext *ctx, LEPUSValueConst this_obj,
                              JSAtom prop, LEPUSValue val, int flags) {
  int ret;
  ret = JS_DefineProperty_GC(
      ctx, this_obj, prop, val, LEPUS_UNDEFINED, LEPUS_UNDEFINED,
      flags | LEPUS_PROP_HAS_VALUE | LEPUS_PROP_HAS_CONFIGURABLE |
          LEPUS_PROP_HAS_WRITABLE | LEPUS_PROP_HAS_ENUMERABLE);
  return ret;
}

int JS_DefinePropertyValueValue_GC(LEPUSContext *ctx, LEPUSValueConst this_obj,
                                   LEPUSValue prop, LEPUSValue val, int flags) {
  JSAtom atom;
  int ret;
  atom = js_value_to_atom_gc(ctx, prop);
  if (unlikely(atom == JS_ATOM_NULL)) {
    return -1;
  }
  HandleScope func_scope(ctx->rt);
  func_scope.PushLEPUSAtom(atom);
  ret = JS_DefinePropertyValue_GC(ctx, this_obj, atom, val, flags);
  return ret;
}

int JS_DefinePropertyValueUint32_GC(LEPUSContext *ctx, LEPUSValueConst this_obj,
                                    uint32_t idx, LEPUSValue val, int flags) {
  return JS_DefinePropertyValueValue_GC(ctx, this_obj, JS_NewUint32(ctx, idx),
                                        val, flags);
}

int JS_DefinePropertyValueInt64_GC(LEPUSContext *ctx, LEPUSValueConst this_obj,
                                   int64_t idx, LEPUSValue val, int flags) {
  return JS_DefinePropertyValueValue_GC(ctx, this_obj, JS_NewInt64_GC(ctx, idx),
                                        val, flags);
}

int JS_DefinePropertyValueStr_GC(LEPUSContext *ctx, LEPUSValueConst this_obj,
                                 const char *prop, LEPUSValue val, int flags) {
  JSAtom atom;
  int ret;
  atom = LEPUS_NewAtom(ctx, prop);
  HandleScope func_scope(ctx->rt);
  func_scope.PushLEPUSAtom(atom);

  ret = JS_DefinePropertyValue_GC(ctx, this_obj, atom, val, flags);
  return ret;
}

/* shortcut to add getter & setter */
int JS_DefinePropertyGetSet_GC(LEPUSContext *ctx, LEPUSValueConst this_obj,
                               JSAtom prop, LEPUSValue getter,
                               LEPUSValue setter, int flags) {
  int ret;
  ret = JS_DefineProperty_GC(
      ctx, this_obj, prop, LEPUS_UNDEFINED, getter, setter,
      flags | LEPUS_PROP_HAS_GET | LEPUS_PROP_HAS_SET |
          LEPUS_PROP_HAS_CONFIGURABLE | LEPUS_PROP_HAS_ENUMERABLE);
  return ret;
}

static int JS_CreateDataPropertyUint32(LEPUSContext *ctx,
                                       LEPUSValueConst this_obj, int64_t idx,
                                       LEPUSValue val, int flags) {
  return JS_DefinePropertyValueValue_GC(
      ctx, this_obj, JS_NewInt64_GC(ctx, idx), val,
      flags | LEPUS_PROP_CONFIGURABLE | LEPUS_PROP_ENUMERABLE |
          LEPUS_PROP_WRITABLE);
}

static BOOL js_object_has_name(LEPUSContext *ctx, LEPUSValueConst obj) {
  JSProperty *pr;
  JSShapeProperty *prs;
  LEPUSValueConst val;
  JSString *p;

  prs = find_own_property(&pr, LEPUS_VALUE_GET_OBJ(obj), JS_ATOM_name);
  if (!prs) return FALSE;
  if ((prs->flags & LEPUS_PROP_TMASK) != LEPUS_PROP_NORMAL) return TRUE;
  val = pr->u.value;
  if (!LEPUS_IsString(val)) return TRUE;
  if (JS_IsSeparableString(val)) {
    return JS_GetSeparableString(val)->len != 0;
  }

  p = LEPUS_VALUE_GET_STRING(val);
  return (p->len != 0);
}

int JS_DefineObjectName_GC(LEPUSContext *ctx, LEPUSValueConst obj, JSAtom name,
                           int flags) {
  // <Primjs begin>
  obj = JSRef2Value(ctx, obj);
  // <Primjs end>
  if (name != JS_ATOM_NULL && LEPUS_IsObject(obj) &&
      !js_object_has_name(ctx, obj) &&
      JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_name,
                                JS_AtomToString_GC(ctx, name), flags) < 0) {
    return -1;
  }
  return 0;
}

int JS_DefineObjectNameComputed_GC(LEPUSContext *ctx, LEPUSValueConst obj,
                                   LEPUSValueConst str, int flags) {
  // <Primjs begin>
  obj = JSRef2Value(ctx, obj);
  // <Primjs end>
  if (LEPUS_IsObject(obj) && !js_object_has_name(ctx, obj)) {
    HandleScope block_scope(ctx->rt);
    JSAtom prop;
    LEPUSValue name_str;
    prop = js_value_to_atom_gc(ctx, str);
    if (prop == JS_ATOM_NULL) return -1;
    block_scope.PushLEPUSAtom(prop);
    name_str = js_get_function_name(ctx, prop);
    if (LEPUS_IsException(name_str)) return -1;
    block_scope.PushHandle(&name_str, HANDLE_TYPE_LEPUS_VALUE);
    if (JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_name, name_str, flags) < 0)
      return -1;
  }
  return 0;
}

#define DEFINE_GLOBAL_LEX_VAR (1 << 7)
#define DEFINE_GLOBAL_FUNC_VAR (1 << 6)

LEPUSValue JS_ThrowSyntaxErrorVarRedeclaration_GC(LEPUSContext *ctx,
                                                  JSAtom prop) {
  return JS_ThrowSyntaxErrorAtom(ctx, "redeclaration of '%s'", prop);
}

/* flags is 0, DEFINE_GLOBAL_LEX_VAR or DEFINE_GLOBAL_FUNC_VAR */
/* XXX: could support exotic global object. */
int JS_CheckDefineGlobalVar_GC(LEPUSContext *ctx, JSAtom prop, int flags) {
  LEPUSObject *p;
  JSShapeProperty *prs;
  char buf[ATOM_GET_STR_BUF_SIZE];

  p = LEPUS_VALUE_GET_OBJ(ctx->global_obj);
  prs = find_own_property1(p, prop);
  /* XXX: should handle LEPUS_PROP_AUTOINIT */
  if (flags & DEFINE_GLOBAL_LEX_VAR) {
    if (prs && !(prs->flags & LEPUS_PROP_CONFIGURABLE)) goto fail_redeclaration;
  } else {
    if (!prs && !p->extensible) goto define_error;
    if (flags & DEFINE_GLOBAL_FUNC_VAR) {
      if (prs) {
        if (!(prs->flags & LEPUS_PROP_CONFIGURABLE) &&
            ((prs->flags & LEPUS_PROP_TMASK) == LEPUS_PROP_GETSET ||
             ((prs->flags & (LEPUS_PROP_WRITABLE | LEPUS_PROP_ENUMERABLE)) !=
              (LEPUS_PROP_WRITABLE | LEPUS_PROP_ENUMERABLE)))) {
        define_error:
          JS_ThrowTypeErrorAtom(ctx, "cannot define variable '%s'", prop);
          return -1;
        }
      }
    }
  }
  /* check if there already is a lexical declaration */
  p = LEPUS_VALUE_GET_OBJ(ctx->global_var_obj);
  prs = find_own_property1(p, prop);
  if (prs) {
  fail_redeclaration:
    JS_ThrowSyntaxErrorVarRedeclaration_GC(ctx, prop);
    return -1;
  }
  return 0;
}

/* def_flags is (0, DEFINE_GLOBAL_LEX_VAR) |
   LEPUS_PROP_CONFIGURABLE | LEPUS_PROP_WRITABLE */
/* XXX: could support exotic global object. */
int JS_DefineGlobalVar_GC(LEPUSContext *ctx, JSAtom prop, int def_flags) {
  LEPUSObject *p;
  JSShapeProperty *prs;
  JSProperty *pr;
  LEPUSValue val = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  int flags;

  if (def_flags & DEFINE_GLOBAL_LEX_VAR) {
    p = LEPUS_VALUE_GET_OBJ(ctx->global_var_obj);
    flags = LEPUS_PROP_ENUMERABLE | (def_flags & LEPUS_PROP_WRITABLE) |
            LEPUS_PROP_CONFIGURABLE;
    val = LEPUS_UNINITIALIZED;
  } else {
    p = LEPUS_VALUE_GET_OBJ(ctx->global_obj);
    flags = LEPUS_PROP_ENUMERABLE | LEPUS_PROP_WRITABLE |
            (def_flags & LEPUS_PROP_CONFIGURABLE);
    val = LEPUS_UNDEFINED;
  }
  prs = find_own_property1(p, prop);
  if (prs) return 0;
  if (!p->extensible) return 0;
  pr = add_property_gc(ctx, p, prop, flags);
  if (unlikely(!pr)) return -1;
  pr->u.value = val;
  return 0;
}

/* 'def_flags' is 0 or LEPUS_PROP_CONFIGURABLE. */
/* XXX: could support exotic global object. */
int JS_DefineGlobalFunction_GC(LEPUSContext *ctx, JSAtom prop,
                               LEPUSValueConst func, int def_flags) {
  LEPUSObject *p;
  JSShapeProperty *prs;
  int flags;

  p = LEPUS_VALUE_GET_OBJ(ctx->global_obj);
  prs = find_own_property1(p, prop);
  flags = LEPUS_PROP_HAS_VALUE | LEPUS_PROP_THROW;
  if (!prs || (prs->flags & LEPUS_PROP_CONFIGURABLE)) {
    flags |= LEPUS_PROP_ENUMERABLE | LEPUS_PROP_WRITABLE | def_flags |
             LEPUS_PROP_HAS_CONFIGURABLE | LEPUS_PROP_HAS_WRITABLE |
             LEPUS_PROP_HAS_ENUMERABLE;
  }
  if (JS_DefineProperty_GC(ctx, ctx->global_obj, prop, func, LEPUS_UNDEFINED,
                           LEPUS_UNDEFINED, flags) < 0)
    return -1;
  return 0;
}

LEPUSValue JS_GetGlobalVar_GC(LEPUSContext *ctx, JSAtom prop,
                              BOOL throw_ref_error) {
  return JS_GetGlobalVarImpl_GC(ctx, prop, throw_ref_error);
}

LEPUSValue JS_GetGlobalVarImpl_GC(LEPUSContext *ctx, JSAtom prop,
                                  BOOL throw_ref_error) {
  LEPUSObject *p;
  JSShapeProperty *prs;
  JSProperty *pr;

  /* no exotic behavior is possible in global_var_obj */
  p = LEPUS_VALUE_GET_OBJ(ctx->global_var_obj);
  prs = find_own_property(&pr, p, prop);
  if (prs) {
    /* XXX: should handle LEPUS_PROP_TMASK properties */
    if (unlikely(LEPUS_IsUninitialized(pr->u.value)))
      return JS_ThrowReferenceErrorUninitialized_GC(ctx, prs->atom);
    return pr->u.value;
  }
  return JS_GetPropertyInternalImpl_GC(ctx, ctx->global_obj, prop,
                                       ctx->global_obj, throw_ref_error);
}

/* construct a reference to a global variable */
int JS_GetGlobalVarRef_GC(LEPUSContext *ctx, JSAtom prop, LEPUSValue *sp) {
  LEPUSObject *p;
  JSShapeProperty *prs;
  JSProperty *pr;

  /* no exotic behavior is possible in global_var_obj */
  p = LEPUS_VALUE_GET_OBJ(ctx->global_var_obj);
  prs = find_own_property(&pr, p, prop);
  if (prs) {
    /* XXX: should handle LEPUS_PROP_AUTOINIT properties? */
    /* XXX: conformance: do these tests in
       OP_put_var_ref/OP_get_var_ref ? */
    if (unlikely(LEPUS_IsUninitialized(pr->u.value))) {
      JS_ThrowReferenceErrorUninitialized_GC(ctx, prs->atom);
      return -1;
    }
    if (unlikely(!(prs->flags & LEPUS_PROP_WRITABLE))) {
      return JS_ThrowTypeErrorReadOnly(ctx, LEPUS_PROP_THROW, prop);
    }
    sp[0] = ctx->global_var_obj;
  } else {
    int ret;
    ret = JS_HasProperty_GC(ctx, ctx->global_obj, prop);
    if (ret < 0) return -1;
    if (ret) {
      sp[0] = ctx->global_obj;
    } else {
      sp[0] = LEPUS_UNDEFINED;
    }
  }
  sp[1] = JS_AtomToValue_GC(ctx, prop);
  return 0;
}

/* use for strict variable access: test if the variable exists */
int JS_CheckGlobalVar_GC(LEPUSContext *ctx, JSAtom prop) {
  LEPUSObject *p;
  JSShapeProperty *prs;
  int ret;

  /* no exotic behavior is possible in global_var_obj */
  p = LEPUS_VALUE_GET_OBJ(ctx->global_var_obj);
  prs = find_own_property1(p, prop);
  if (prs) {
    ret = TRUE;
  } else {
    ret = JS_HasProperty_GC(ctx, ctx->global_obj, prop);
    if (ret < 0) return -1;
  }
  return ret;
}

/* flag = 0: normal variable write
   flag = 1: initialize lexical variable
   flag = 2: normal variable write, strict check was done before
   flag = 3: add property in global var obj
*/
int JS_SetGlobalVar_GC(LEPUSContext *ctx, JSAtom prop, LEPUSValue val,
                       int flag) {
  LEPUSObject *p;
  JSShapeProperty *prs;
  JSProperty *pr;
  int flags;

  if (flag == 3) {
    flags = LEPUS_PROP_THROW_STRICT;
    return JS_SetPropertyInternal_GC(ctx, ctx->global_var_obj, prop, val,
                                     flags);
  }
  /* no exotic behavior is possible in global_var_obj */
  p = LEPUS_VALUE_GET_OBJ(ctx->global_var_obj);
  prs = find_own_property(&pr, p, prop);
  if (prs) {
    /* XXX: should handle LEPUS_PROP_AUTOINIT properties? */
    if (flag != 1) {
      if (unlikely(LEPUS_IsUninitialized(pr->u.value))) {
        JS_ThrowReferenceErrorUninitialized_GC(ctx, prs->atom);
        return -1;
      }
      if (unlikely(!(prs->flags & LEPUS_PROP_WRITABLE))) {
        return JS_ThrowTypeErrorReadOnly(ctx, LEPUS_PROP_THROW, prop);
      }
    }
    set_value_gc(ctx, &pr->u.value, val);
    return 0;
  }

  flags = LEPUS_PROP_THROW_STRICT;
  if (flag != 2 && is_strict_mode(ctx)) flags |= LEPUS_PROP_NO_ADD;
  return JS_SetPropertyInternal_GC(ctx, ctx->global_obj, prop, val, flags);
}

/* return -1, FALSE or TRUE. return FALSE if not configurable or
   invalid object. return -1 in case of exception.
   flags can be 0, LEPUS_PROP_THROW or LEPUS_PROP_THROW_STRICT */
int JS_DeleteProperty_GC(LEPUSContext *ctx, LEPUSValueConst obj, JSAtom prop,
                         int flags) {
  obj = JS_ToObject_GC(ctx, obj);
  if (LEPUS_IsException(obj)) return -1;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSObject *p = LEPUS_VALUE_GET_OBJ(obj);
  int res = delete_property(ctx, p, prop);
  if (res != FALSE) return res;
  if ((flags & LEPUS_PROP_THROW) ||
      ((flags & LEPUS_PROP_THROW_STRICT) && is_strict_mode(ctx))) {
    LEPUS_ThrowTypeError(ctx, "could not delete property");
    return -1;
  }
  return FALSE;
}

static int JS_DeletePropertyInt64(LEPUSContext *ctx, LEPUSValueConst obj,
                                  int64_t idx, int flags) {
  JSAtom prop;
  int res;

  if ((uint64_t)idx <= JS_ATOM_MAX_INT) {
    /* fast path for fast arrays */
    return JS_DeleteProperty_GC(ctx, obj, __JS_AtomFromUInt32(idx), flags);
  }
  prop = JS_NewAtomInt64(ctx, idx);
  if (prop == JS_ATOM_NULL) return -1;
  HandleScope func_scope(ctx->rt);
  func_scope.PushLEPUSAtom(prop);
  res = JS_DeleteProperty_GC(ctx, obj, prop, flags);
  return res;
}

static BOOL JS_IsCFunction(LEPUSContext *ctx, LEPUSValueConst val,
                           LEPUSCFunction *func, int magic) {
  LEPUSObject *p;
  if (LEPUS_VALUE_IS_NOT_OBJECT(val)) return FALSE;
  p = LEPUS_VALUE_GET_OBJ(val);
  if (p->class_id == JS_CLASS_C_FUNCTION)
    return (p->u.cfunc.c_function.generic == func && p->u.cfunc.magic == magic);
  else
    return FALSE;
}

/* used to avoid catching interrupt exceptions */
BOOL JS_IsUncatchableError_GC(LEPUSContext *ctx, LEPUSValueConst val) {
  LEPUSObject *p;
  if (LEPUS_VALUE_IS_NOT_OBJECT(val)) return FALSE;
  p = LEPUS_VALUE_GET_OBJ(val);
  return p->class_id == JS_CLASS_ERROR && p->is_uncatchable_error;
}

static void JS_SetUncatchableError(LEPUSContext *ctx, LEPUSValueConst val,
                                   BOOL flag) {
  LEPUSObject *p;
  if (LEPUS_VALUE_IS_NOT_OBJECT(val)) return;
  p = LEPUS_VALUE_GET_OBJ(val);
  if (p->class_id == JS_CLASS_ERROR) p->is_uncatchable_error = flag;
}

LEPUSValue JS_ToPrimitiveFree_GC(LEPUSContext *ctx, LEPUSValue val, int hint) {
  int i;
  BOOL force_ordinary;

  JSAtom method_name;  // assignment as const atom
  LEPUSValue method, ret;
  if (LEPUS_VALUE_IS_NOT_OBJECT(val)) return val;
  force_ordinary = hint & HINT_FORCE_ORDINARY;
  hint &= ~HINT_FORCE_ORDINARY;
  if (!force_ordinary) {
    method =
        JS_GetPropertyInternal_GC(ctx, val, JS_ATOM_Symbol_toPrimitive, val, 0);
    if (LEPUS_IsException(method)) goto exception;
    /* ECMA says *If exoticToPrim is not undefined* but tests in
       test262 use null as a non callable converter */
    if (!LEPUS_IsUndefined(method) && !LEPUS_IsNull(method)) {
      JSAtom atom;  // assignment as const atom
      LEPUSValue arg;
      switch (hint) {
        case HINT_STRING:
          atom = JS_ATOM_string;
          break;
        case HINT_NUMBER:
          atom = JS_ATOM_number;
          break;
        default:
        case HINT_NONE:
          atom = JS_ATOM_default;
          break;
      }
      arg = JS_AtomToString_GC(ctx, atom);
      HandleScope block_scope(ctx, &arg, HANDLE_TYPE_LEPUS_VALUE);
      ret = JS_CallFree_GC(ctx, method, val, 1,
                           reinterpret_cast<LEPUSValueConst *>(&arg));
      if (LEPUS_IsException(ret)) goto exception;
      if (LEPUS_VALUE_IS_NOT_OBJECT(ret)) return ret;
      return LEPUS_ThrowTypeError(ctx, "toPrimitive");
    }
  }
  if (hint != HINT_STRING) hint = HINT_NUMBER;
  for (i = 0; i < 2; i++) {
    if ((i ^ hint) == 0) {
      method_name = JS_ATOM_toString;
    } else {
      method_name = JS_ATOM_valueOf;
    }
    method = JS_GetPropertyInternal_GC(ctx, val, method_name, val, 0);
    if (LEPUS_IsException(method)) goto exception;
    if (LEPUS_IsFunction(ctx, method)) {
      ret = JS_CallFree_GC(ctx, method, val, 0, NULL);
      if (LEPUS_IsException(ret)) goto exception;
      if (LEPUS_VALUE_IS_NOT_OBJECT(ret)) {
        return ret;
      }
    }
  }
  LEPUS_ThrowTypeError(ctx, "toPrimitive");
exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue JS_ToPrimitive(LEPUSContext *ctx, LEPUSValueConst val,
                                 int hint) {
  return JS_ToPrimitiveFree_GC(ctx, val, hint);
}

int JS_ToBoolFree_GC(LEPUSContext *ctx, LEPUSValue val) {
  int64_t tag = LEPUS_VALUE_GET_TAG(val);
  switch (tag) {
    case LEPUS_TAG_BOOL:
      return LEPUS_VALUE_GET_BOOL(val);
    case LEPUS_TAG_INT:
      return LEPUS_VALUE_GET_INT(val) != 0;
    case LEPUS_TAG_NULL:
    case LEPUS_TAG_UNDEFINED:
      return FALSE;
    case LEPUS_TAG_EXCEPTION:
      return -1;
    case LEPUS_TAG_STRING: {
      BOOL ret = LEPUS_VALUE_GET_STRING(val)->len != 0;
      return ret;
    }
    case LEPUS_TAG_SEPARABLE_STRING: {
      BOOL ret = JS_GetSeparableString(val)->len != 0;
      return ret;
    }
    case LEPUS_TAG_BIG_INT: {
      JSBigInt *p = static_cast<JSBigInt *>(LEPUS_VALUE_GET_PTR(val));
      BOOL ret = FALSE;
      int32_t i;
      /* fail safe: we assume it is not necessarily
   normalized. Beginning from the MSB ensures that the
   test is fast. */
      for (i = p->len - 1; i >= 0; --i) {
        if (p->tab[i] != 0) {
          ret = TRUE;
          break;
        }
      }
      return ret;
    }
    default:
      if (LEPUS_TAG_IS_FLOAT64(tag)) {
        double d = LEPUS_VALUE_GET_FLOAT64(val);
        return !isnan(d) && d != 0;
      } else {
        return TRUE;
      }
  }
}

int JS_ToBool_GC(LEPUSContext *ctx, LEPUSValueConst val) {
  return JS_ToBoolFree_GC(ctx, val);
}

static LEPUSValue JS_ToNumberHintFree(LEPUSContext *ctx, LEPUSValue val,
                                      JSToNumberHintEnum flag) {
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  int64_t tag;
  LEPUSValue ret = LEPUS_UNDEFINED;
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);

redo:
  tag = LEPUS_VALUE_GET_NORM_TAG(val);
  switch (tag) {
    case LEPUS_TAG_BIG_INT:
      if (flag != TON_FLAG_NUMERIC) {
        return LEPUS_ThrowTypeError(ctx, "cannot convert bigint to number");
      }
      ret = val;
      break;
    case LEPUS_TAG_FLOAT64:
    case LEPUS_TAG_INT:
    case LEPUS_TAG_EXCEPTION:
      ret = val;
      break;
    case LEPUS_TAG_BOOL:
      ret = LEPUS_NewInt32(ctx, LEPUS_VALUE_GET_BOOL(val));
      break;
    case LEPUS_TAG_NULL:
      ret = LEPUS_NewInt32(ctx, 0);
      break;
    case LEPUS_TAG_UNDEFINED:
      ret = LEPUS_NAN;
      break;
    case LEPUS_TAG_OBJECT:
      val = JS_ToPrimitiveFree_GC(ctx, val, HINT_NUMBER);
      if (LEPUS_IsException(val)) return LEPUS_EXCEPTION;
      goto redo;
    case LEPUS_TAG_STRING: {
      const char *str;
      const char *p;
      size_t len;

      str = JS_ToCStringLen2_GC(ctx, &len, val, 0);
      if (!str) return LEPUS_EXCEPTION;
      HandleScope block_scope(ctx, &str, HANDLE_TYPE_CSTRING);
      p = str;
      p += skip_spaces(p);
      if ((p - str) == len) {
        ret = LEPUS_NewInt32(ctx, 0);
      } else {
        int flags = ATOD_ACCEPT_BIN_OCT;
        ret = js_atof(ctx, p, &p, 0, flags);
        if (!LEPUS_IsException(ret)) {
          p += skip_spaces(p);
          if ((p - str) != len) {
            ret = LEPUS_NAN;
          }
        }
      }
    } break;
    case LEPUS_TAG_SEPARABLE_STRING: {
      auto content = JS_GetSeparableStringContent_GC(ctx, val);
      HandleScope block_scope(ctx, &content, HANDLE_TYPE_LEPUS_VALUE);
      ret = JS_ToNumberHintFree(ctx, content, flag);
    } break;
    case LEPUS_TAG_SYMBOL:
      return LEPUS_ThrowTypeError(ctx, "cannot convert symbol to number");
    default:
      ret = LEPUS_NAN;
      break;
  }
  return ret;
}

static LEPUSValue JS_ToNumberFree(LEPUSContext *ctx, LEPUSValue val) {
  return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMBER);
}

static LEPUSValue JS_ToNumericFree(LEPUSContext *ctx, LEPUSValue val) {
  return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMERIC);
}

static LEPUSValue JS_ToNumeric(LEPUSContext *ctx, LEPUSValueConst val) {
  return JS_ToNumericFree(ctx, val);
}

static __exception int __JS_ToFloat64Free(LEPUSContext *ctx, double *pres,
                                          LEPUSValue val) {
  double d;
  int64_t tag;

  val = JS_ToNumberFree(ctx, val);
  if (LEPUS_IsException(val)) goto fail;
  tag = LEPUS_VALUE_GET_NORM_TAG(val);
  switch (tag) {
    case LEPUS_TAG_INT:
      d = LEPUS_VALUE_GET_INT(val);
      break;
    case LEPUS_TAG_FLOAT64:
      d = LEPUS_VALUE_GET_FLOAT64(val);
      break;
    default:
      abort();
  }
  *pres = d;
  return 0;
fail:
  *pres = LEPUS_FLOAT64_NAN;
  return -1;
}

static inline int JS_ToFloat64Free(LEPUSContext *ctx, double *pres,
                                   LEPUSValue val) {
  if (LEPUS_VALUE_IS_FLOAT64(val)) {
    *pres = LEPUS_VALUE_GET_FLOAT64(val);
    return 0;
  } else if (LEPUS_VALUE_IS_INT(val)) {
    *pres = LEPUS_VALUE_GET_INT(val);
    return 0;
  } else if (LEPUS_VALUE_IS_BOOL(val)) {
    *pres = LEPUS_VALUE_GET_BOOL(val);
    return 0;
  } else if (LEPUS_VALUE_IS_NULL(val)) {
    *pres = 0;
    return 0;
  } else {
    return __JS_ToFloat64Free(ctx, pres, val);
  }
}

int JS_ToFloat64_GC(LEPUSContext *ctx, double *pres, LEPUSValueConst val) {
  return JS_ToFloat64Free(ctx, pres, val);
}

static LEPUSValue JS_ToNumber(LEPUSContext *ctx, LEPUSValueConst val) {
  return JS_ToNumberFree(ctx, val);
}

static __attribute__((unused)) LEPUSValue JS_ToIntegerFree(LEPUSContext *ctx,
                                                           LEPUSValue val) {
  int64_t tag;
  LEPUSValue ret = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &ret, HANDLE_TYPE_LEPUS_VALUE);

redo:
  tag = LEPUS_VALUE_GET_NORM_TAG(val);
  switch (tag) {
    case LEPUS_TAG_INT:
      ret = LEPUS_NewInt32(ctx, LEPUS_VALUE_GET_INT(val));
      break;
    case LEPUS_TAG_BOOL:
      ret = LEPUS_NewInt32(ctx, LEPUS_VALUE_GET_BOOL(val));
      break;
    case LEPUS_TAG_NULL:
    case LEPUS_TAG_UNDEFINED:
      ret = LEPUS_NewInt32(ctx, 0);
      break;
    case LEPUS_TAG_FLOAT64: {
      double d = LEPUS_VALUE_GET_FLOAT64(val);
      if (isnan(d)) {
        ret = LEPUS_NewInt32(ctx, 0);
      } else {
        /* convert -0 to +0 */
        /* XXX: should not be done here ? */
        d = trunc(d) + 0.0;
        ret = LEPUS_NewFloat64(ctx, d);
      }
    } break;
    default:
      val = JS_ToNumberFree(ctx, val);
      if (LEPUS_IsException(val)) return val;
      goto redo;
  }
  return ret;
}

/* Note: the integer value is satured to 32 bits */
static int JS_ToInt32SatFree(LEPUSContext *ctx, int *pres, LEPUSValue val) {
  int64_t tag;
  int ret;

redo:
  tag = LEPUS_VALUE_GET_NORM_TAG(val);
  switch (tag) {
    case LEPUS_TAG_INT:
      ret = LEPUS_VALUE_GET_INT(val);
      break;
    case LEPUS_TAG_BOOL:
      ret = LEPUS_VALUE_GET_BOOL(val);
      break;
    case LEPUS_TAG_NULL:
    case LEPUS_TAG_UNDEFINED:
      ret = 0;
      break;
    case LEPUS_TAG_EXCEPTION:
      *pres = 0;
      return -1;
    case LEPUS_TAG_FLOAT64: {
      double d = LEPUS_VALUE_GET_FLOAT64(val);
      if (isnan(d)) {
        ret = 0;
      } else {
        if (d < INT32_MIN)
          ret = INT32_MIN;
        else if (d > INT32_MAX)
          ret = INT32_MAX;
        else
          ret = static_cast<int>(d);
      }
    } break;
    default:
      val = JS_ToNumberFree(ctx, val);
      if (LEPUS_IsException(val)) {
        *pres = 0;
        return -1;
      }
      goto redo;
  }
  *pres = ret;
  return 0;
}

static int JS_ToInt32Sat(LEPUSContext *ctx, int *pres, LEPUSValueConst val) {
  return JS_ToInt32SatFree(ctx, pres, val);
}

static int JS_ToInt32Clamp(LEPUSContext *ctx, int *pres, LEPUSValueConst val,
                           int min, int max, int min_offset) {
  int res = JS_ToInt32SatFree(ctx, pres, val);
  if (res == 0) {
    if (*pres < min) {
      *pres += min_offset;
      if (*pres < min) *pres = min;
    } else {
      if (*pres > max) *pres = max;
    }
  }
  return res;
}

static int JS_ToInt64SatFree(LEPUSContext *ctx, int64_t *pres, LEPUSValue val) {
  int64_t tag;

redo:
  tag = LEPUS_VALUE_GET_NORM_TAG(val);
  switch (tag) {
    case LEPUS_TAG_INT:
      *pres = LEPUS_VALUE_GET_INT(val);
      return 0;
    case LEPUS_TAG_BOOL:
      *pres = LEPUS_VALUE_GET_BOOL(val);
      return 0;
    case LEPUS_TAG_NULL:
    case LEPUS_TAG_UNDEFINED:
      *pres = 0;
      return 0;
    case LEPUS_TAG_EXCEPTION:
      *pres = 0;
      return -1;
    case LEPUS_TAG_FLOAT64: {
      double d = LEPUS_VALUE_GET_FLOAT64(val);
      if (isnan(d)) {
        *pres = 0;
      } else {
        if (d < INT64_MIN)
          *pres = INT64_MIN;
        else if (d > INT64_MAX)
          *pres = INT64_MAX;
        else
          *pres = (int64_t)d;
      }
    }
      return 0;
    default:
      val = JS_ToNumberFree(ctx, val);
      if (LEPUS_IsException(val)) {
        *pres = 0;
        return -1;
      }
      goto redo;
  }
}

static int JS_ToInt64Sat(LEPUSContext *ctx, int64_t *pres,
                         LEPUSValueConst val) {
  return JS_ToInt64SatFree(ctx, pres, val);
}

static int JS_ToInt64Clamp(LEPUSContext *ctx, int64_t *pres,
                           LEPUSValueConst val, int64_t min, int64_t max,
                           int64_t neg_offset) {
  int res = JS_ToInt64SatFree(ctx, pres, val);
  if (res == 0) {
    if (*pres < 0) *pres += neg_offset;
    if (*pres < min)
      *pres = min;
    else if (*pres > max)
      *pres = max;
  }
  return res;
}

/* Same as JS_ToInt32Free() but with a 64 bit result. Return (<0, 0)
   in case of exception */
static int JS_ToInt64Free(LEPUSContext *ctx, int64_t *pres, LEPUSValue val) {
  int64_t tag;
  int64_t ret;

redo:
  tag = LEPUS_VALUE_GET_NORM_TAG(val);
  switch (tag) {
    case LEPUS_TAG_INT:
      ret = LEPUS_VALUE_GET_INT(val);
      break;
    case LEPUS_TAG_BOOL:
      ret = LEPUS_VALUE_GET_BOOL(val);
      break;
    case LEPUS_TAG_NULL:
    case LEPUS_TAG_UNDEFINED:
      ret = 0;
      break;
    case LEPUS_TAG_FLOAT64: {
      JSFloat64Union u;
      double d;
      int e;
      d = LEPUS_VALUE_GET_FLOAT64(val);
      u.d = d;
      /* we avoid doing fmod(x, 2^64) */
      e = (u.u64 >> 52) & 0x7ff;
      if (likely(e <= (1023 + 62))) {
        /* fast case */
        ret = (int64_t)d;
      } else if (e <= (1023 + 62 + 53)) {
        uint64_t v;
        /* remainder modulo 2^64 */
        v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52);
        ret = v << ((e - 1023) - 52);
        /* take the sign into account */
        if (u.u64 >> 63) ret = -ret;
      } else {
        ret = 0; /* also handles NaN and +inf */
      }
    } break;
    default:
      val = JS_ToNumberFree(ctx, val);
      if (LEPUS_IsException(val)) {
        *pres = 0;
        return -1;
      }
      goto redo;
  }
  *pres = ret;
  return 0;
}

int JS_ToInt64_GC(LEPUSContext *ctx, int64_t *pres, LEPUSValueConst val) {
  return JS_ToInt64Free(ctx, pres, val);
}

/* return (<0, 0) in case of exception */
static int JS_ToInt32Free(LEPUSContext *ctx, int32_t *pres, LEPUSValue val) {
  int64_t tag;
  int32_t ret;

redo:
  tag = LEPUS_VALUE_GET_NORM_TAG(val);
  switch (tag) {
    case LEPUS_TAG_INT:
      ret = LEPUS_VALUE_GET_INT(val);
      break;
    case LEPUS_TAG_BOOL:
      ret = LEPUS_VALUE_GET_BOOL(val);
      break;
    case LEPUS_TAG_NULL:
    case LEPUS_TAG_UNDEFINED:
      ret = 0;
      break;
    case LEPUS_TAG_FLOAT64: {
      JSFloat64Union u;
      double d;
      int e;
      d = LEPUS_VALUE_GET_FLOAT64(val);
      u.d = d;
      /* we avoid doing fmod(x, 2^32) */
      e = (u.u64 >> 52) & 0x7ff;
      if (likely(e <= (1023 + 30))) {
        /* fast case */
        ret = (int32_t)d;
      } else if (e <= (1023 + 30 + 53)) {
        uint64_t v;
        /* remainder modulo 2^32 */
        v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52);
        v = v << ((e - 1023) - 52 + 32);
        ret = v >> 32;
        /* take the sign into account */
        if (u.u64 >> 63) ret = -ret;
      } else {
        ret = 0; /* also handles NaN and +inf */
      }
    } break;
    default:
      val = JS_ToNumberFree(ctx, val);
      if (LEPUS_IsException(val)) {
        *pres = 0;
        return -1;
      }
      goto redo;
  }
  *pres = ret;
  return 0;
}

int JS_ToInt32_GC(LEPUSContext *ctx, int32_t *pres, LEPUSValueConst val) {
  return JS_ToInt32Free(ctx, pres, val);
}

static inline int JS_ToUint32Free(LEPUSContext *ctx, uint32_t *pres,
                                  LEPUSValue val) {
  return JS_ToInt32Free(ctx, reinterpret_cast<int32_t *>(pres), val);
}

static int JS_ToUint8ClampFree(LEPUSContext *ctx, int32_t *pres,
                               LEPUSValue val) {
  int64_t tag;
  int res;

redo:
  tag = LEPUS_VALUE_GET_NORM_TAG(val);
  switch (tag) {
    case LEPUS_TAG_INT:
      res = LEPUS_VALUE_GET_INT(val);
      res = max_int(0, min_int(255, res));
      break;
    case LEPUS_TAG_BOOL:
      res = LEPUS_VALUE_GET_BOOL(val);
      res = max_int(0, min_int(255, res));
      break;
    case LEPUS_TAG_NULL:
    case LEPUS_TAG_UNDEFINED:
      res = 0;
      res = max_int(0, min_int(255, res));
      break;
    case LEPUS_TAG_FLOAT64: {
      double d = LEPUS_VALUE_GET_FLOAT64(val);
      if (isnan(d)) {
        res = 0;
      } else {
        if (d < 0)
          res = 0;
        else if (d > 255)
          res = 255;
        else
          res = lrint(d);
      }
    } break;
    default:
      val = JS_ToNumberFree(ctx, val);
      if (LEPUS_IsException(val)) {
        *pres = 0;
        return -1;
      }
      goto redo;
  }
  *pres = res;
  return 0;
}

QJS_STATIC __exception int JS_ToArrayLengthFree(LEPUSContext *ctx,
                                                uint32_t *plen, LEPUSValue val,
                                                BOOL is_array_ctor) {
  int64_t tag;
  uint32_t len;

  tag = LEPUS_VALUE_GET_TAG(val);
  switch (tag) {
    case LEPUS_TAG_INT: {
      int v;
      v = LEPUS_VALUE_GET_INT(val);
      if (v < 0) goto fail;
      len = v;
    } break;
    case LEPUS_TAG_BOOL: {
      int v;
      v = LEPUS_VALUE_GET_BOOL(val);
      len = v;
    } break;
    case LEPUS_TAG_NULL: {
      int v;
      v = 0;
      len = v;
    } break;
    default:
      if (LEPUS_TAG_IS_FLOAT64(tag)) {
        double d;
        d = LEPUS_VALUE_GET_FLOAT64(val);
        len = (uint32_t)d;
        if (len != d) goto fail;
      } else {
        uint32_t len1;
        if (is_array_ctor) {
          val = JS_ToNumberFree(ctx, val);
          if (LEPUS_IsException(val)) return -1;
          /* cannot recurse because val is a number */
          if (JS_ToArrayLengthFree(ctx, &len, val, TRUE)) return -1;
        } else {
          /* legacy behavior: must do the conversion twice and compare */
          if (JS_ToInt32_GC(ctx, reinterpret_cast<int32_t *>(&len), val)) {
            return -1;
          }
          val = JS_ToNumberFree(ctx, val);
          if (LEPUS_IsException(val)) return -1;
          /* cannot recurse because val is a number */
          if (JS_ToArrayLengthFree(ctx, &len1, val, FALSE)) return -1;
          if (len1 != len) {
          fail:
            LEPUS_ThrowRangeError(ctx, "invalid array length");
            return -1;
          }
        }
      }
      break;
  }
  *plen = len;
  return 0;
}

#define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1)

int JS_ToIndex_GC(LEPUSContext *ctx, uint64_t *plen, LEPUSValueConst val) {
  int64_t v;
  if (JS_ToInt64Sat(ctx, &v, val)) return -1;
  if (v < 0 || v > MAX_SAFE_INTEGER) {
    LEPUS_ThrowRangeError(ctx, "invalid array index");
    *plen = 0;
    return -1;
  }
  *plen = v;
  return 0;
}

/* convert a value to a length between 0 and MAX_SAFE_INTEGER.
   return -1 for exception */
static __exception int JS_ToLengthFree(LEPUSContext *ctx, int64_t *plen,
                                       LEPUSValue val) {
  int res = JS_ToInt64Clamp(ctx, plen, val, 0, MAX_SAFE_INTEGER, 0);
  return res;
}

/* Note: can return an exception */
/* XXX: bignum case */
static int JS_NumberIsInteger(LEPUSContext *ctx, LEPUSValueConst val) {
  double d;
  if (!LEPUS_IsNumber(val)) return FALSE;
  if (unlikely(JS_ToFloat64_GC(ctx, &d, val))) return -1;
  return isfinite(d) && floor(d) == d;
}

static BOOL JS_NumberIsNegativeOrMinusZero(LEPUSContext *ctx,
                                           LEPUSValueConst val) {
  int64_t tag;

  tag = LEPUS_VALUE_GET_NORM_TAG(val);
  switch (tag) {
    case LEPUS_TAG_INT: {
      int v;
      v = LEPUS_VALUE_GET_INT(val);
      return (v < 0);
    }
    case LEPUS_TAG_FLOAT64: {
      JSFloat64Union u;
      u.d = LEPUS_VALUE_GET_FLOAT64(val);
      return (u.u64 >> 63);
    }
    case LEPUS_TAG_BIG_INT: {
      JSBigInt *p = LEPUS_VALUE_GET_BIGINT(val);
      /* Note: integer zeros are not necessarily positive */
      return js_bigint_sign(p);
    }
    default:
      return FALSE;
  }
}

static LEPUSValue JS_ToStringInternal(LEPUSContext *ctx, LEPUSValueConst val,
                                      BOOL is_ToPropertyKey) {
  int64_t tag;
  const char *str;
  char buf[32];

  tag = LEPUS_VALUE_GET_NORM_TAG(val);
  switch (tag) {
    case LEPUS_TAG_SEPARABLE_STRING:
      return JS_GetSeparableStringContent_GC(ctx, val);
    case LEPUS_TAG_STRING:
      return val;
    case LEPUS_TAG_INT:
      snprintf(buf, sizeof(buf), "%d", LEPUS_VALUE_GET_INT(val));
      str = buf;
      goto new_string;
    case LEPUS_TAG_BOOL:
      return JS_AtomToString_GC(
          ctx, LEPUS_VALUE_GET_BOOL(val) ? JS_ATOM_true : JS_ATOM_false);
    case LEPUS_TAG_NULL:
      return JS_AtomToString_GC(ctx, JS_ATOM_null);
    case LEPUS_TAG_UNDEFINED:
      return JS_AtomToString_GC(ctx, JS_ATOM_undefined);
    case LEPUS_TAG_EXCEPTION:
      return LEPUS_EXCEPTION;
    case LEPUS_TAG_OBJECT: {
      LEPUSValue val1, ret;
      val1 = JS_ToPrimitive(ctx, val, HINT_STRING);
      if (LEPUS_IsException(val1)) return val1;
      HandleScope block_scope(ctx, &val1, HANDLE_TYPE_LEPUS_VALUE);
      ret = JS_ToStringInternal(ctx, val1, is_ToPropertyKey);
      return ret;
    } break;
    case LEPUS_TAG_FUNCTION_BYTECODE:
      str = "[function bytecode]";
      goto new_string;
    case LEPUS_TAG_SYMBOL:
      if (is_ToPropertyKey) {
        return val;
      } else {
        return LEPUS_ThrowTypeError(ctx, "cannot convert symbol to string");
      }
    case LEPUS_TAG_FLOAT64:
      return js_dtoa2(ctx, LEPUS_VALUE_GET_FLOAT64(val), 10, 0,
                      JS_DTOA_FORMAT_FREE);
    case LEPUS_TAG_BIG_INT:
      return js_bigint_to_string(ctx, val);
    default:
      str = "[unsupported type]";
    new_string:
      return JS_NewString_GC(ctx, str);
  }
}

LEPUSValue JS_ToString_GC(LEPUSContext *ctx, LEPUSValueConst val) {
  return JS_ToStringInternal(ctx, val, FALSE);
}

static LEPUSValue JS_ToStringFree(LEPUSContext *ctx, LEPUSValue val) {
  LEPUSValue ret;
  ret = JS_ToString_GC(ctx, val);
  return ret;
}

static LEPUSValue JS_ToLocaleStringFree(LEPUSContext *ctx, LEPUSValue val) {
  if (LEPUS_IsUndefined(val) || LEPUS_IsNull(val))
    return JS_ToStringFree(ctx, val);
  return JS_InvokeFree(ctx, val, JS_ATOM_toLocaleString, 0, NULL);
}

LEPUSValue JS_ToPropertyKey_GC(LEPUSContext *ctx, LEPUSValueConst val) {
  return JS_ToStringInternal(ctx, val, TRUE);
}

static LEPUSValue JS_ToStringCheckObject(LEPUSContext *ctx,
                                         LEPUSValueConst val) {
  if (LEPUS_VALUE_IS_NULL(val) || LEPUS_VALUE_IS_UNDEFINED(val))
    return LEPUS_ThrowTypeError(ctx, "null or undefined are forbidden");
  return JS_ToString_GC(ctx, val);
}

static LEPUSValue JS_ToQuotedString(LEPUSContext *ctx, LEPUSValueConst val1) {
  LEPUSValue val;
  JSString *p;
  int i;
  uint32_t c;
  StringBuffer b_s, *b = &b_s;
  char buf[16];

  val = JS_ToStringCheckObject(ctx, val1);
  if (LEPUS_IsException(val)) return val;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_STRING(val);

  if (string_buffer_init(ctx, b, p->len + 2)) goto fail;
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);

  if (string_buffer_putc8(b, '\"')) goto fail;
  for (i = 0; i < p->len;) {
    c = string_getc(p, &i);
    switch (c) {
      case '\t':
        c = 't';
        goto quote;
      case '\r':
        c = 'r';
        goto quote;
      case '\n':
        c = 'n';
        goto quote;
      case '\b':
        c = 'b';
        goto quote;
      case '\f':
        c = 'f';
        goto quote;
      case '\"':
      case '\\':
      quote:
        if (string_buffer_putc8(b, '\\')) goto fail;
        if (string_buffer_putc8(b, c)) goto fail;
        break;
      default:
        if (c < 32 || (c >= 0xd800 && c < 0xe000)) {
          snprintf(buf, sizeof(buf), "\\u%04x", c);
          if (string_buffer_puts8(b, buf)) goto fail;
        } else {
          if (string_buffer_putc(b, c)) goto fail;
        }
        break;
    }
  }
  if (string_buffer_putc8(b, '\"')) goto fail;
  return string_buffer_end(b);
fail:
  b->str = NULL;
  return LEPUS_EXCEPTION;
}
// <Primjs end>

/* return -1 if exception (proxy case) or TRUE/FALSE */
int JS_IsArray_GC(LEPUSContext *ctx, LEPUSValueConst val) {
  LEPUSObject *p;
  if (LEPUS_VALUE_IS_OBJECT(val)) {
    p = LEPUS_VALUE_GET_OBJ(val);
    if (unlikely(p->class_id == JS_CLASS_PROXY))
      return js_proxy_isArray(ctx, val);
    else
      return p->class_id == JS_CLASS_ARRAY;
  } else {
    return FALSE;
  }
}

static LEPUSValue JS_ToBigInt(LEPUSContext *ctx, LEPUSValueConst val) {
  return JS_ToBigIntFree(ctx, val);
}

// <Primjs begin>
#ifdef ENABLE_LEPUSNG

void JS_SetStringCache_GC(LEPUSContext *ctx, LEPUSValue val, void *p) {
  if (JS_IsSeparableString(val)) {
    auto content = JS_GetSeparableString(val)->flat_content;
    if (LEPUS_IsUndefined(content)) {
      return;
    }
    val = content;
  } else if (!LEPUS_VALUE_IS_STRING(val)) {
    return;
  }
  JSString *str = LEPUS_VALUE_GET_STRING(val);
  void *old_ptr = str->cache_;
  str->cache_ = p;
  ctx->rt->js_callbacks_.free_str_cache(old_ptr, p);
  return;
}

void __attribute__((unused)) JS_FreeStringCache(LEPUSRuntime *rt, JSString *p) {
  if (p->cache_ && (rt->js_callbacks_.free_str_cache)) {
    rt->js_callbacks_.free_str_cache(p->cache_, NULL);
    p->cache_ = NULL;
  }
}

void *LEPUS_GetStringCache_GC(LEPUSValue val) {
  if (JS_IsSeparableString(val)) {
    auto content = JS_GetSeparableString(val)->flat_content;
    if (LEPUS_IsUndefined(content)) {
      return nullptr;
    }
    val = content;
  } else if (!LEPUS_VALUE_IS_STRING(val)) {
    return nullptr;
  }
  JSString *str = LEPUS_VALUE_GET_STRING(val);
  return str->cache_;
}
#endif
// <Primjs end>

/* XXX: Should take LEPUSValueConst arguments */
static BOOL js_strict_eq2(LEPUSContext *ctx, LEPUSValue op1, LEPUSValue op2,
                          JSStrictEqModeEnum eq_mode) {
  BOOL res;
  int64_t tag1, tag2;
  double d1, d2;

  tag1 = LEPUS_VALUE_GET_NORM_TAG(op1);
  tag2 = LEPUS_VALUE_GET_NORM_TAG(op2);
  switch (tag1) {
    case LEPUS_TAG_BOOL:
      if (tag1 != tag2) {
        res = FALSE;
      } else {
        res = LEPUS_VALUE_GET_BOOL(op1) == LEPUS_VALUE_GET_BOOL(op2);
        goto done_no_free;
      }
      break;
    case LEPUS_TAG_NULL:
    case LEPUS_TAG_UNDEFINED:
      res = (tag1 == tag2);
      break;
    case LEPUS_TAG_STRING: {
      JSString *p1, *p2;
      if (tag2 == LEPUS_TAG_SEPARABLE_STRING) {
        p1 = LEPUS_VALUE_GET_STRING(op1);
        auto *separable_string = JS_GetSeparableString(op2);
        if (p1->len != separable_string->len) {
          res = FALSE;
        } else {
          auto content = JS_GetSeparableStringContent_GC(ctx, op2);
          HandleScope block_scope(ctx, &content, HANDLE_TYPE_LEPUS_VALUE);
          res = js_strict_eq2(ctx, op1, content, eq_mode);
        }
      } else if (tag1 != tag2) {
        res = FALSE;
      } else {
        p1 = LEPUS_VALUE_GET_STRING(op1);
        p2 = LEPUS_VALUE_GET_STRING(op2);
        // <Primjs change>
        if (p1 == p2) {
          res = TRUE;
        } else if (p1->atom_type == JS_ATOM_TYPE_STRING &&
                   p2->atom_type == JS_ATOM_TYPE_STRING) {
          res = FALSE;
        } else {
          res = (js_string_compare(ctx, p1, p2) == 0);
        }
      }
    } break;
    case LEPUS_TAG_SEPARABLE_STRING: {
      if (tag2 == LEPUS_TAG_SEPARABLE_STRING) {
        auto *separable_string_1 = JS_GetSeparableString(op1);
        auto *separable_string_2 = JS_GetSeparableString(op2);
        if (separable_string_1->len != separable_string_2->len) {
          res = FALSE;
        } else {
          auto content_1 = JS_GetSeparableStringContent_GC(ctx, op1);
          auto content_2 = JS_GetSeparableStringContent_GC(ctx, op2);
          HandleScope block_scope(ctx, &content_1, HANDLE_TYPE_LEPUS_VALUE);
          block_scope.PushHandle(&content_2, HANDLE_TYPE_LEPUS_VALUE);
          res = js_strict_eq2(ctx, content_1, content_2, eq_mode);
        }
      } else if (tag2 == LEPUS_TAG_STRING) {
        res = js_strict_eq2(ctx, op2, op1, eq_mode);
      } else {
        res = FALSE;
      }
    } break;
    case LEPUS_TAG_SYMBOL: {
      JSAtomStruct *p1, *p2;
      if (tag1 != tag2) {
        res = FALSE;
      } else {
        p1 = static_cast<JSAtomStruct *>(LEPUS_VALUE_GET_PTR(op1));
        p2 = static_cast<JSAtomStruct *>(LEPUS_VALUE_GET_PTR(op2));
        res = (p1 == p2);
      }
    } break;
    case LEPUS_TAG_OBJECT:
      if (tag1 != tag2)
        res = FALSE;
      else
        res = LEPUS_VALUE_GET_OBJ(op1) == LEPUS_VALUE_GET_OBJ(op2);
      break;
#ifdef ENABLE_LEPUSNG
    case LEPUS_TAG_LEPUS_REF: {
      if (tag1 != tag2)
        res = FALSE;
      else
        res = (LEPUS_GetLepusRefPoint(op1) == LEPUS_GetLepusRefPoint(op2));
    } break;
    case LEPUS_TAG_LEPUS_CPOINTER: {
      if (tag1 != tag2) {
        res = FALSE;
      } else {
        res = (LEPUS_VALUE_GET_PTR(op1) == LEPUS_VALUE_GET_PTR(op2));
      }
    } break;
#endif
    case LEPUS_TAG_INT:
      d1 = LEPUS_VALUE_GET_INT(op1);
      if (tag2 == LEPUS_TAG_INT) {
        d2 = LEPUS_VALUE_GET_INT(op2);
        goto number_test;
      } else if (tag2 == LEPUS_TAG_FLOAT64) {
        d2 = LEPUS_VALUE_GET_FLOAT64(op2);
        goto number_test;
      } else {
        res = FALSE;
      }
      break;
    case LEPUS_TAG_FLOAT64:
      d1 = LEPUS_VALUE_GET_FLOAT64(op1);
      if (tag2 == LEPUS_TAG_FLOAT64) {
        d2 = LEPUS_VALUE_GET_FLOAT64(op2);
      } else if (tag2 == LEPUS_TAG_INT) {
        d2 = LEPUS_VALUE_GET_INT(op2);
      } else {
        res = FALSE;
        break;
      }
    number_test:
      if (unlikely(eq_mode >= JS_EQ_SAME_VALUE)) {
        JSFloat64Union u1, u2;
        /* NaN is not always normalized, so this test is necessary */
        if (isnan(d1) || isnan(d2)) {
          res = isnan(d1) == isnan(d2);
        } else if (eq_mode == JS_EQ_SAME_VALUE_ZERO) {
          res = (d1 == d2); /* +0 == -0 */
        } else {
          u1.d = d1;
          u2.d = d2;
          res = (u1.u64 == u2.u64); /* +0 != -0 */
        }
      } else {
        res = (d1 == d2); /* if NaN return false and +0 == -0 */
      }
      goto done_no_free;
    case LEPUS_TAG_BIG_INT: {
      JSBigIntBuf buf1, buf2;
      JSBigInt *p1, *p2;

      if (tag2 != LEPUS_TAG_BIG_INT) {
        res = FALSE;
        break;
      }
      p1 = LEPUS_VALUE_GET_BIGINT(op1);
      p2 = LEPUS_VALUE_GET_BIGINT(op2);
      res = (js_bigint_cmp(ctx, p1, p2) == 0);
    } break;
    default:
      res = FALSE;
      break;
  }
done_no_free:
  return res;
}

static BOOL js_strict_eq(LEPUSContext *ctx, LEPUSValue op1, LEPUSValue op2) {
  return js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT);
}

static BOOL js_same_value(LEPUSContext *ctx, LEPUSValueConst op1,
                          LEPUSValueConst op2) {
  return js_strict_eq2(ctx, op1, op2, JS_EQ_SAME_VALUE);
}

static BOOL js_same_value_zero(LEPUSContext *ctx, LEPUSValueConst op1,
                               LEPUSValueConst op2) {
  return js_strict_eq2(ctx, op1, op2, JS_EQ_SAME_VALUE_ZERO);
}

static no_inline int js_strict_eq_slow(LEPUSContext *ctx, LEPUSValue *sp,
                                       BOOL is_neq) {
  BOOL res;
  res = js_strict_eq(ctx, sp[-2], sp[-1]);
  sp[-2] = LEPUS_NewBool(ctx, res ^ is_neq);
  return 0;
}

static __exception int js_operator_in(LEPUSContext *ctx, LEPUSValue *sp) {
  HandleScope func_scope(ctx);
  LEPUSValue op1, op2;
  JSAtom atom;
  int ret;

  op1 = sp[-2];
  op2 = sp[-1];

  if (LEPUS_VALUE_IS_NOT_OBJECT(op2) && !LEPUS_VALUE_IS_LEPUS_REF(op2)) {
    atom = js_value_to_atom_gc(ctx, op1);
    if (unlikely(atom == JS_ATOM_NULL)) {
      LEPUS_ThrowTypeError(ctx, "invalid 'in' operand search for null");
      return -1;
    }
    func_scope.PushLEPUSAtom(atom);
    const char *msg = JS_AtomToCString_GC(ctx, atom);
    char buffer[200];
    if (msg) {
      snprintf(buffer, 199, "invalid 'in' operand search for '%s'", msg);
    } else {
      snprintf(buffer, 199, "invalid 'in' operand search for null");
    }
    LEPUS_ThrowTypeError(ctx, "%s", buffer);
    return -1;
  }

  atom = js_value_to_atom_gc(ctx, op1);
  if (unlikely(atom == JS_ATOM_NULL)) return -1;
  func_scope.PushLEPUSAtom(atom);
  ret = JS_HasProperty_GC(ctx, op2, atom);
  if (ret < 0) return -1;
  sp[-2] = LEPUS_NewBool(ctx, ret);
  return 0;
}

static __exception int js_has_unscopable(LEPUSContext *ctx, LEPUSValueConst obj,
                                         JSAtom atom) {
  LEPUSValue arr, val;
  int ret;

  arr = JS_GetPropertyInternal_GC(ctx, obj, JS_ATOM_Symbol_unscopables, obj, 0);
  if (LEPUS_IsException(arr)) return -1;
  ret = 0;
  if (LEPUS_IsObject(arr)) {
    val = JS_GetPropertyInternal_GC(ctx, arr, atom, arr, 0);
    ret = JS_ToBoolFree_GC(ctx, val);
  }
  return ret;
}

static __exception int js_operator_instanceof(LEPUSContext *ctx,
                                              LEPUSValue *sp) {
  LEPUSValue op1, op2;
  BOOL ret;

  op1 = sp[-2];
  op2 = sp[-1];
  LEPUSValueConst obj = JSRef2Value(ctx, op2);
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  ret = JS_IsInstanceOf_GC(ctx, op1, obj);
  if (ret < 0) return ret;
  sp[-2] = LEPUS_NewBool(ctx, ret);
  return 0;
}

__exception int js_operator_typeof_gc(LEPUSContext *ctx, LEPUSValue op1) {
  JSAtom atom;
  int64_t tag;

  tag = LEPUS_VALUE_GET_NORM_TAG(op1);
  switch (tag) {
// <Primjs begin>
#ifdef ENABLE_LEPUSNG
    case LEPUS_TAG_LEPUS_REF:
      if (ctx && (JS_LepusRefIsArray(ctx->rt, op1) ||
                  JS_LepusRefIsTable(ctx->rt, op1))) {
        atom = JS_ATOM_object;
      } else {
        atom = JS_ATOM_unknown;
      }
      break;
#endif
      // <Primjs end>
    case LEPUS_TAG_BIG_INT:
      atom = LEPUS_NewAtom(ctx, "bigint");
      break;
    case LEPUS_TAG_INT:
    case LEPUS_TAG_FLOAT64:
      atom = JS_ATOM_number;
      break;
    case LEPUS_TAG_UNDEFINED:
      atom = JS_ATOM_undefined;
      break;
    case LEPUS_TAG_BOOL:
      atom = JS_ATOM_boolean;
      break;
    case LEPUS_TAG_STRING:
    case LEPUS_TAG_SEPARABLE_STRING:
      atom = JS_ATOM_string;
      break;
    case LEPUS_TAG_OBJECT:
      if (LEPUS_IsFunction(ctx, op1))
        atom = JS_ATOM_function;
      else
        goto obj_type;
      break;
    case LEPUS_TAG_NULL:
    obj_type:
      atom = JS_ATOM_object;
      break;
    case LEPUS_TAG_SYMBOL:
      atom = JS_ATOM_symbol;
      break;
    default:
      atom = JS_ATOM_unknown;
      break;
  }
  return atom;
}

static __exception int js_operator_delete(LEPUSContext *ctx, LEPUSValue *sp) {
  HandleScope func_scope(ctx);
  LEPUSValue op1, op2;
  JSAtom atom;
  int ret;

  op1 = sp[-2];
  op2 = sp[-1];
  atom = js_value_to_atom_gc(ctx, op2);
  if (unlikely(atom == JS_ATOM_NULL)) return -1;
  func_scope.PushLEPUSAtom(atom);
  ret = JS_DeleteProperty_GC(ctx, op1, atom, LEPUS_PROP_THROW_STRICT);
  if (unlikely(ret < 0)) return -1;
  sp[-2] = LEPUS_NewBool(ctx, ret);
  return 0;
}

QJS_HIDE LEPUSValue js_function_proto_fileName_GC(LEPUSContext *ctx,
                                                  LEPUSValueConst this_val) {
  LEPUSFunctionBytecode *b = JS_GetFunctionBytecode(this_val);
  if (b && b->has_debug) {
    return JS_AtomToString_GC(ctx, b->debug.filename);
  }
  return LEPUS_UNDEFINED;
}

static LEPUSValue js_function_proto_lineNumber(LEPUSContext *ctx,
                                               LEPUSValueConst this_val) {
  LEPUSFunctionBytecode *b = JS_GetFunctionBytecode(this_val);
  if (b && b->has_debug) {
    return LEPUS_NewInt32(ctx, b->debug.line_num);
  }
  return LEPUS_UNDEFINED;
}

static int js_arguments_define_own_property(LEPUSContext *ctx,
                                            LEPUSValueConst this_obj,
                                            JSAtom prop, LEPUSValueConst val,
                                            LEPUSValueConst getter,
                                            LEPUSValueConst setter, int flags) {
  LEPUSObject *p;
  uint32_t idx;
  p = LEPUS_VALUE_GET_OBJ(this_obj);
  /* convert to normal array when redefining an existing numeric field */
  if (p->fast_array && JS_AtomIsArrayIndex(ctx, &idx, prop) &&
      idx < p->u.array.count) {
    if (convert_fast_array_to_array(ctx, p)) return -1;
  }
  /* run the default define own property */
  return JS_DefineProperty_GC(ctx, this_obj, prop, val, getter, setter,
                              flags | LEPUS_PROP_NO_EXOTIC);
}

LEPUSValue js_build_arguments_gc(LEPUSContext *ctx, int argc,
                                 LEPUSValueConst *argv) {
  LEPUSValue val, *tab;
  JSProperty *pr;
  LEPUSObject *p;
  int i;

  val = JS_NewObjectProtoClass_GC(ctx, ctx->class_proto[JS_CLASS_OBJECT],
                                  JS_CLASS_ARGUMENTS);
  if (LEPUS_IsException(val)) return val;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_OBJ(val);

  /* add the length field (cannot fail) */
  pr = add_property_gc(ctx, p, JS_ATOM_length,
                       LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE);
  pr->u.value = LEPUS_NewInt32(ctx, argc);

  /* initialize the fast array part */
  tab = NULL;
  if (argc > 0) {
    tab = static_cast<LEPUSValue *>(
        lepus_malloc(ctx, sizeof(tab[0]) * argc, ALLOC_TAG_WITHOUT_PTR));
    if (!tab) {
      return LEPUS_EXCEPTION;
    }
    for (i = 0; i < argc; i++) {
      tab[i] = argv[i];
    }
  }
  p->u.array.u.values = tab;
  p->u.array.count = argc;

  JS_DefinePropertyValue_GC(ctx, val, JS_ATOM_Symbol_iterator,
                            ctx->array_proto_values,
                            LEPUS_PROP_CONFIGURABLE | LEPUS_PROP_WRITABLE);
  /* add callee property to throw a TypeError in strict mode */
  JS_DefineProperty_GC(ctx, val, JS_ATOM_callee, LEPUS_UNDEFINED,
                       ctx->throw_type_error, ctx->throw_type_error,
                       LEPUS_PROP_HAS_GET | LEPUS_PROP_HAS_SET);
  return val;
}

#define GLOBAL_VAR_OFFSET 0x40000000
#define ARGUMENT_VAR_OFFSET 0x20000000

#ifdef ENABLE_TRACING_GC
LEPUSObject *ShallowCloneObj(LEPUSContext *ctx, LEPUSObject *src) {
  LEPUSObject *p = src;
  JSProperty *new_prop = static_cast<JSProperty *>(lepus_malloc(
      ctx, sizeof(JSProperty) * p->shape->prop_size, ALLOC_TAG_WITHOUT_PTR));
  memcpy(new_prop, p->prop, p->shape->prop_size * sizeof(JSProperty));

  HandleScope func_scope(ctx, new_prop, HANDLE_TYPE_DIR_HEAP_OBJ);
  LEPUSObject *new_p = static_cast<LEPUSObject *>(
      lepus_malloc(ctx, sizeof(LEPUSObject), ALLOC_TAG_LEPUSObject));
  memcpy(new_p, p, sizeof(LEPUSObject));
  js_dup_shape(p->shape);
  new_p->prop = new_prop;
  return new_p;
}
void CopyProps(LEPUSContext *ctx, int argc, int arg_count, LEPUSStackFrame *sf,
               LEPUSObject *new_p, LEPUSValueConst *argv) {
  int i;
  new_p->prop[2].u.value = ctx->rt->current_stack_frame->cur_func;
  size_t start_idx = 3;
  JSVarRef *var_ref = nullptr;
  for (i = 0; i < arg_count; i++) {
    var_ref = get_var_ref(ctx, sf, i, TRUE);
    new_p->prop[start_idx++].u.var_ref = var_ref;
  }

  /* the arguments not mapped to the arguments of the function can
     be normal properties */
  for (i = arg_count; i < argc; i++) {
    new_p->prop[start_idx++].u.value = argv[i];
  }
}
#endif
/* legacy arguments object: add references to the function arguments */
LEPUSValue js_build_mapped_arguments_gc(LEPUSContext *ctx, int argc,
                                        LEPUSValueConst *argv,
                                        LEPUSStackFrame *sf, int arg_count) {
#ifdef ENABLE_TRACING_GC
  switch (argc) {
    case 0: {
      if (ctx->rt->boilerplateArg0 != nullptr) {
        LEPUSObject *new_p = ShallowCloneObj(ctx, ctx->rt->boilerplateArg0);
        CopyProps(ctx, argc, arg_count, sf, new_p, argv);
        return LEPUS_MKPTR(LEPUS_TAG_OBJECT, new_p);
      }
      break;
    }
    case 1: {
      if (ctx->rt->boilerplateArg1 != nullptr && arg_count == 0) {
        LEPUSObject *new_p = ShallowCloneObj(ctx, ctx->rt->boilerplateArg1);
        CopyProps(ctx, argc, arg_count, sf, new_p, argv);
        return LEPUS_MKPTR(LEPUS_TAG_OBJECT, new_p);
      }
      break;
    }
    case 2: {
      if (ctx->rt->boilerplateArg2 != nullptr && arg_count == 0) {
        LEPUSObject *new_p = ShallowCloneObj(ctx, ctx->rt->boilerplateArg2);
        CopyProps(ctx, argc, arg_count, sf, new_p, argv);
        return LEPUS_MKPTR(LEPUS_TAG_OBJECT, new_p);
      }
      break;
    }
    case 3: {
      if (ctx->rt->boilerplateArg3 != nullptr && arg_count == 0) {
        LEPUSObject *new_p = ShallowCloneObj(ctx, ctx->rt->boilerplateArg3);
        CopyProps(ctx, argc, arg_count, sf, new_p, argv);
        return LEPUS_MKPTR(LEPUS_TAG_OBJECT, new_p);
      }
      break;
    }
  }
#endif
  LEPUSValue val;
  JSProperty *pr;
  LEPUSObject *p;
  int i;

  val = JS_NewObjectProtoClass_GC(ctx, ctx->class_proto[JS_CLASS_OBJECT],
                                  JS_CLASS_MAPPED_ARGUMENTS);
  if (LEPUS_IsException(val)) return val;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_OBJ(val);

  /* add the length field (cannot fail) */
  pr = add_property_gc(ctx, p, JS_ATOM_length,
                       LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE);
  pr->u.value = LEPUS_NewInt32(ctx, argc);

  JS_DefinePropertyValue_GC(ctx, val, JS_ATOM_Symbol_iterator,
                            ctx->array_proto_values,
                            LEPUS_PROP_CONFIGURABLE | LEPUS_PROP_WRITABLE);
  /* callee returns this function in non strict mode */
  JS_DefinePropertyValue_GC(ctx, val, JS_ATOM_callee,
                            ctx->rt->current_stack_frame->cur_func,
                            LEPUS_PROP_CONFIGURABLE | LEPUS_PROP_WRITABLE);

  JSVarRef *var_ref = nullptr;
  func_scope.PushHandle(&var_ref, HANDLE_TYPE_HEAP_OBJ);
  for (i = 0; i < arg_count; i++) {
    var_ref = get_var_ref(ctx, sf, i, TRUE);
    if (!var_ref) goto fail;
    pr = add_property_gc(ctx, p, __JS_AtomFromUInt32(i),
                         LEPUS_PROP_C_W_E | LEPUS_PROP_VARREF);
    if (!pr) {
      goto fail;
    }
    pr->u.var_ref = var_ref;
  }

  /* the arguments not mapped to the arguments of the function can
     be normal properties */
  for (i = arg_count; i < argc; i++) {
    if (JS_DefinePropertyValueUint32_GC(ctx, val, i, argv[i],
                                        LEPUS_PROP_C_W_E) < 0)
      goto fail;
  }

#ifdef ENABLE_TRACING_GC
  if (argc == 0) {
    ctx->rt->boilerplateArg0 = ShallowCloneObj(ctx, p);
  } else if (arg_count == 0 && argc == 1) {
    ctx->rt->boilerplateArg1 = ShallowCloneObj(ctx, p);
  } else if (arg_count == 0 && argc == 2) {
    ctx->rt->boilerplateArg2 = ShallowCloneObj(ctx, p);
  } else if (arg_count == 0 && argc == 3) {
    ctx->rt->boilerplateArg3 = ShallowCloneObj(ctx, p);
  }
#endif
  return val;
fail:
  return LEPUS_EXCEPTION;
}

LEPUSValue js_build_rest_gc(LEPUSContext *ctx, int first, int argc,
                            LEPUSValueConst *argv) {
  LEPUSValue val;
  int i, ret;

  val = JS_NewArray_GC(ctx);
  if (LEPUS_IsException(val)) return val;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  for (i = first; i < argc; i++) {
    ret = JS_DefinePropertyValueUint32_GC(ctx, val, i - first, argv[i],
                                          LEPUS_PROP_C_W_E);
    if (ret < 0) {
      return LEPUS_EXCEPTION;
    }
  }
  return val;
}

static LEPUSValue build_for_in_iterator(LEPUSContext *ctx, LEPUSValue obj) {
  LEPUSObject *p, *p1;
  LEPUSPropertyEnum *tab_atom = nullptr;
  HandleScope func_scope(ctx, &tab_atom, HANDLE_TYPE_HEAP_OBJ);
  int i;
  LEPUSValue enum_obj;
  JSForInIterator *it;
  int64_t tag;
  uint32_t tab_atom_count;

  tag = LEPUS_VALUE_GET_TAG(obj);
  if (tag != LEPUS_TAG_OBJECT && tag != LEPUS_TAG_NULL &&
      tag != LEPUS_TAG_UNDEFINED) {
    obj = JS_ToObjectFree(ctx, obj);
    func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  }

  it = static_cast<JSForInIterator *>(
      lepus_malloc(ctx, sizeof(*it), ALLOC_TAG_JSForInIterator));
  if (!it) {
    return LEPUS_EXCEPTION;
  }
  func_scope.PushHandle(it, HANDLE_TYPE_DIR_HEAP_OBJ);
  it->obj = LEPUS_UNDEFINED;
  enum_obj =
      JS_NewObjectProtoClass_GC(ctx, LEPUS_NULL, JS_CLASS_FOR_IN_ITERATOR);
  if (LEPUS_IsException(enum_obj)) {
    return LEPUS_EXCEPTION;
  }
  func_scope.PushHandle(&enum_obj, HANDLE_TYPE_LEPUS_VALUE);
  it->is_array = FALSE;
  it->obj = obj;
  it->idx = 0;
  p = LEPUS_VALUE_GET_OBJ(enum_obj);
  p->u.for_in_iterator = it;

  if (tag == LEPUS_TAG_NULL || tag == LEPUS_TAG_UNDEFINED) return enum_obj;

  p = LEPUS_VALUE_GET_OBJ(obj);

  /* fast path: assume no enumerable properties in the prototype chain */
  p1 = p->shape->proto;
  while (p1 != NULL) {
    if (JS_GetOwnPropertyNamesInternal(
            ctx, &tab_atom, &tab_atom_count, p1,
            LEPUS_GPN_STRING_MASK | LEPUS_GPN_ENUM_ONLY))
      goto fail;
    if (tab_atom_count != 0) {
      goto slow_path;
    }
    p1 = p1->shape->proto;
  }
  if (p->fast_array) {
    JSShape *sh;
    JSShapeProperty *prs;
    /* check that there are no enumerable normal fields */
    sh = p->shape;
    for (i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) {
      if (prs->flags & LEPUS_PROP_ENUMERABLE) goto normal_case;
    }
    /* 10.4.5.1 + 10.4.5.10 + 10.4.5.11, if detached, return undefined */
    /* for fast arrays, we only store the number of elements */
    it->is_array = TRUE;
    it->array_length = p->u.array.count;
  } else {
  normal_case:
    if (JS_GetOwnPropertyNamesInternal(
            ctx, &tab_atom, &tab_atom_count, p,
            LEPUS_GPN_STRING_MASK | LEPUS_GPN_ENUM_ONLY))
      goto fail;
    for (i = 0; i < tab_atom_count; i++) {
      JS_SetPropertyInternal_GC(ctx, enum_obj, tab_atom[i].atom, LEPUS_NULL, 0);
    }
  }
  return enum_obj;

slow_path:
  /* non enumerable properties hide the enumerables ones in the
     prototype chain */
  while (p != NULL) {
    if (JS_GetOwnPropertyNamesInternal(
            ctx, &tab_atom, &tab_atom_count, p,
            LEPUS_GPN_STRING_MASK | LEPUS_GPN_SET_ENUM))
      goto fail;
    for (i = 0; i < tab_atom_count; i++) {
      JS_DefinePropertyValue_GC(
          ctx, enum_obj, tab_atom[i].atom, LEPUS_NULL,
          (tab_atom[i].is_enumerable ? LEPUS_PROP_ENUMERABLE : 0));
    }
    p = p->shape->proto;
  }
  return enum_obj;

fail:
  return LEPUS_EXCEPTION;
}

/* obj -> enum_obj */
static __exception int js_for_in_start(LEPUSContext *ctx, LEPUSValue *sp) {
  sp[-1] = build_for_in_iterator(ctx, sp[-1]);
  if (LEPUS_IsException(sp[-1])) return -1;
  return 0;
}

/* enum_obj -> enum_obj value done */
__exception int js_for_in_next_gc(LEPUSContext *ctx, LEPUSValue *sp) {
  LEPUSValueConst enum_obj;
  LEPUSObject *p;
  JSAtom prop;
  JSForInIterator *it;
  int ret;
  HandleScope func_scope(ctx->rt);

  enum_obj = sp[-1];
  /* fail safe */
  if (LEPUS_VALUE_IS_NOT_OBJECT(enum_obj)) goto done;
  p = LEPUS_VALUE_GET_OBJ(enum_obj);
  if (p->class_id != JS_CLASS_FOR_IN_ITERATOR) goto done;
  it = p->u.for_in_iterator;

  for (;;) {
    if (it->is_array) {
      if (it->idx >= it->array_length) goto done;
      prop = __JS_AtomFromUInt32(it->idx);
      it->idx++;
      func_scope.PushLEPUSAtom(prop);
    } else {
      JSShape *sh = p->shape;
      JSShapeProperty *prs;
      if (it->idx >= sh->prop_count) goto done;
      prs = get_shape_prop(sh) + it->idx;
      prop = prs->atom;
      it->idx++;
      if (prop == JS_ATOM_NULL || !(prs->flags & LEPUS_PROP_ENUMERABLE))
        continue;
    }
    /* check if the property was deleted */
    ret = JS_HasProperty_GC(ctx, it->obj, prop);
    if (ret < 0) return ret;
    if (ret) break;
  }
  /* return the property */
  sp[0] = JS_AtomToValue_GC(ctx, prop);
  sp[1] = LEPUS_FALSE;
  return 0;
done:
  /* return the end */
  sp[0] = LEPUS_UNDEFINED;
  sp[1] = LEPUS_TRUE;
  return 0;
}

/* obj -> enum_rec (3 slots) */
__exception int js_for_of_start_gc(LEPUSContext *ctx, LEPUSValue *sp,
                                   BOOL is_async) {
  LEPUSValue op1 = LEPUS_UNDEFINED, obj = LEPUS_UNDEFINED,
             method = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &op1, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&method, HANDLE_TYPE_LEPUS_VALUE);
  op1 = sp[-1];
  obj = JS_GetIterator(ctx, op1, is_async);
  if (LEPUS_IsException(obj)) return -1;
  sp[-1] = obj;
  method = JS_GetPropertyInternal_GC(ctx, obj, JS_ATOM_next, obj, 0);
  if (LEPUS_IsException(method)) return -1;
  sp[0] = method;
  return 0;
}

/* enum_rec -> enum_rec value done */
__exception int js_for_of_next_gc(LEPUSContext *ctx, LEPUSValue *sp,
                                  int offset) {
  LEPUSValue value = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &value, HANDLE_TYPE_LEPUS_VALUE);
  int done = 1;

  if (likely(!LEPUS_IsUndefined(sp[offset]))) {
    value = JS_IteratorNext(ctx, sp[offset], sp[offset + 1], 0, NULL, &done);
    if (LEPUS_IsException(value)) done = -1;
    if (done) {
      /* value is LEPUS_UNDEFINED or LEPUS_EXCEPTION */
      /* replace the iteration object with undefined */
      sp[offset] = LEPUS_UNDEFINED;
      if (done < 0) {
        return -1;
      } else {
        value = LEPUS_UNDEFINED;
      }
    }
  }
  sp[0] = value;
  sp[1] = LEPUS_NewBool(ctx, done);
  return 0;
}

__exception int js_for_await_of_next_gc(LEPUSContext *ctx, LEPUSValue *sp) {
  LEPUSValue result;
  result = JS_Call_GC(ctx, sp[-2], sp[-3], 0, NULL);
  if (LEPUS_IsException(result)) return -1;
  sp[0] = result;
  return 0;
}

static LEPUSValue JS_IteratorGetCompleteValue(LEPUSContext *ctx,
                                              LEPUSValueConst obj,
                                              BOOL *pdone) {
  LEPUSValue done_val = LEPUS_UNDEFINED, value = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &done_val, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&value, HANDLE_TYPE_LEPUS_VALUE);
  BOOL done;
  done_val = JS_GetPropertyInternal_GC(ctx, obj, JS_ATOM_done, obj, 0);
  if (LEPUS_IsException(done_val)) goto fail;
  done = JS_ToBoolFree_GC(ctx, done_val);
  value = JS_GetPropertyInternal_GC(ctx, obj, JS_ATOM_value, obj, 0);
  if (LEPUS_IsException(value)) goto fail;
  *pdone = done;
  return value;
fail:
  *pdone = FALSE;
  return LEPUS_EXCEPTION;
}

__exception int js_iterator_get_value_done_gc(LEPUSContext *ctx,
                                              LEPUSValue *sp) {
  LEPUSValue obj, value;
  BOOL done;
  obj = sp[-1];
  if (!LEPUS_IsObject(obj)) {
    LEPUS_ThrowTypeError(ctx, "iterator must return an object");
    return -1;
  }
  value = JS_IteratorGetCompleteValue(ctx, obj, &done);
  if (LEPUS_IsException(value)) return -1;
  sp[-1] = value;
  sp[0] = LEPUS_NewBool(ctx, done);
  return 0;
}

static LEPUSValue js_create_iterator_result(LEPUSContext *ctx, LEPUSValue val,
                                            BOOL done) {
  LEPUSValue obj;
  obj = JS_NewObject_GC(ctx);
  if (LEPUS_IsException(obj)) {
    return obj;
  }
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  if (JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_value, val,
                                LEPUS_PROP_C_W_E) < 0) {
    goto fail;
  }
  if (JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_done,
                                LEPUS_NewBool(ctx, done),
                                LEPUS_PROP_C_W_E) < 0) {
  fail:
    return LEPUS_EXCEPTION;
  }
  return obj;
}

static LEPUSValue js_array_iterator_next(LEPUSContext *ctx,
                                         LEPUSValueConst this_val, int argc,
                                         LEPUSValueConst *argv, BOOL *pdone,
                                         int magic);

static LEPUSValue js_create_array_iterator(LEPUSContext *ctx,
                                           LEPUSValueConst this_val, int argc,
                                           LEPUSValueConst *argv, int magic);

static BOOL js_is_fast_array(LEPUSContext *ctx, LEPUSValueConst obj) {
  /* Try and handle fast arrays explicitly */
  if (LEPUS_VALUE_IS_OBJECT(obj)) {
    LEPUSObject *p = LEPUS_VALUE_GET_OBJ(obj);
    if (p->class_id == JS_CLASS_ARRAY && p->fast_array) {
      return TRUE;
    }
  }
  return FALSE;
}

/* Access an Array's internal LEPUSValue array if available */
static BOOL js_get_fast_array(LEPUSContext *ctx, LEPUSValueConst obj,
                              LEPUSValue **arrpp, uint32_t *countp) {
  /* Try and handle fast arrays explicitly */
  if (LEPUS_VALUE_IS_OBJECT(obj)) {
    LEPUSObject *p = LEPUS_VALUE_GET_OBJ(obj);
    if (p->class_id == JS_CLASS_ARRAY && p->fast_array) {
      *countp = p->u.array.count;
      *arrpp = p->u.array.u.values;
      return TRUE;
    }
  }
  return FALSE;
}

__exception int js_append_enumerate_gc(LEPUSContext *ctx, LEPUSValue *sp) {
  LEPUSValue iterator = LEPUS_UNDEFINED, enumobj = LEPUS_UNDEFINED,
             method = LEPUS_UNDEFINED, value = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &iterator, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&enumobj, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&method, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&value, HANDLE_TYPE_LEPUS_VALUE);
  int pos, is_array_iterator;
  LEPUSValue *arrp;
  uint32_t i, count32;

  if (!LEPUS_VALUE_IS_INT(sp[-2])) {
    LEPUS_ThrowInternalError(ctx, "invalid index for append");
    return -1;
  }

  pos = LEPUS_VALUE_GET_INT(sp[-2]);

  /* XXX: further optimisations:
     - use ctx->array_proto_values?
     - check if array_iterator_prototype next method is built-in and
       avoid constructing actual iterator object?
     - build this into js_for_of_start_gc and use in all `for (x of o)` loops
   */
  iterator = JS_GetPropertyInternal_GC(ctx, sp[-1], JS_ATOM_Symbol_iterator,
                                       sp[-1], 0);
  if (LEPUS_IsException(iterator)) return -1;
  LEPUSCFunctionType ft = {.generic_magic = js_create_array_iterator};
  is_array_iterator =
      JS_IsCFunction(ctx, iterator, ft.generic, JS_ITERATOR_KIND_VALUE);

  enumobj = JS_GetIterator(ctx, sp[-1], FALSE);
  if (LEPUS_IsException(enumobj)) return -1;
  method = JS_GetPropertyInternal_GC(ctx, enumobj, JS_ATOM_next, enumobj, 0);
  if (LEPUS_IsException(method)) {
    return -1;
  }
  LEPUSCFunctionType ft2 = {.iterator_next = js_array_iterator_next};
  if (is_array_iterator && JS_IsCFunction(ctx, method, ft2.generic, 0) &&
      js_get_fast_array(ctx, sp[-1], &arrp, &count32)) {
    int64_t len;
    /* Handle fast arrays explicitly */
    if (js_get_length64(ctx, &len, sp[-1])) goto exception;
    for (i = 0; i < count32; i++) {
      if (JS_DefinePropertyValueUint32_GC(ctx, sp[-3], pos++, arrp[i],
                                          LEPUS_PROP_C_W_E) < 0)
        goto exception;
    }
    // https://262.ecma-international.org/6.0/#sec-argument-lists-runtime-semantics-argumentlistevaluation
    if (len != count32) {
      goto general_case;
    }
  } else {
  general_case:
    for (;;) {
      BOOL done;
      value = JS_IteratorNext(ctx, enumobj, method, 0, NULL, &done);
      if (LEPUS_IsException(value)) goto exception;
      if (done) {
        /* value is LEPUS_UNDEFINED */
        break;
      }
      if (JS_DefinePropertyValueUint32_GC(ctx, sp[-3], pos++, value,
                                          LEPUS_PROP_C_W_E) < 0)
        goto exception;
    }
  }
  sp[-2] = LEPUS_NewInt32(ctx, pos);
  return 0;

exception:
  JS_IteratorClose(ctx, enumobj, TRUE);
  return -1;
}

static __exception int JS_CopyDataProperties(LEPUSContext *ctx,
                                             LEPUSValueConst target,
                                             LEPUSValueConst source,
                                             LEPUSValueConst excluded,
                                             BOOL setprop) {
  LEPUSPropertyEnum *tab_atom = nullptr;
  HandleScope func_scope(ctx, &tab_atom, HANDLE_TYPE_HEAP_OBJ);
  LEPUSValue val = LEPUS_UNDEFINED;
  uint32_t i, tab_atom_count;
  LEPUSObject *p;
  LEPUSObject *pexcl = NULL;
  int ret = 0, flags;
// <Primjs begin>
#ifdef ENABLE_LEPUSNG
  if (LEPUS_VALUE_IS_LEPUS_REF(source)) {
    if (ctx->rt->primjs_callbacks_.js_get_own_property_names(
            ctx, source, &tab_atom_count, &tab_atom, LEPUS_GPN_ENUM_ONLY)) {
      return -1;
    }
    goto CopyValueProperties;
  }
#endif
  // <Primjs end>

  if (LEPUS_VALUE_IS_NOT_OBJECT(source)) return 0;

  p = LEPUS_VALUE_GET_OBJ(source);
  if (JS_GetOwnPropertyNamesInternal(
          ctx, &tab_atom, &tab_atom_count, p,
          LEPUS_GPN_STRING_MASK | LEPUS_GPN_SYMBOL_MASK | LEPUS_GPN_ENUM_ONLY))
    return -1;

#ifdef ENABLE_LEPUSNG
CopyValueProperties:
#endif

  if (LEPUS_VALUE_IS_OBJECT(excluded)) pexcl = LEPUS_VALUE_GET_OBJ(excluded);

  flags = LEPUS_PROP_C_W_E;

  for (i = 0; i < tab_atom_count; i++) {
    if (pexcl) {
      ret = JS_GetOwnPropertyInternal(ctx, NULL, pexcl, tab_atom[i].atom);
      if (ret) {
        if (ret < 0) break;
        ret = 0;
        continue;
      }
    }
    ret = -1;
    val = JS_GetPropertyInternal_GC(ctx, source, tab_atom[i].atom, source, 0);
    if (LEPUS_IsException(val)) break;
    if (setprop)
      ret = JS_SetPropertyInternal_GC(ctx, target, tab_atom[i].atom, val,
                                      LEPUS_PROP_THROW);
    else
      ret =
          JS_DefinePropertyValue_GC(ctx, target, tab_atom[i].atom, val, flags);
    if (ret < 0) break;
    ret = 0;
  }
  return ret;
}

static LEPUSValue js_instantiate_prototype(LEPUSContext *ctx, LEPUSObject *p,
                                           JSAtom atom, void *opaque) {
  LEPUSValue obj, this_val;
  int ret;

  this_val = LEPUS_MKPTR(LEPUS_TAG_OBJECT, p);
  HandleScope func_scope(ctx, &this_val, HANDLE_TYPE_LEPUS_VALUE);
  obj = JS_NewObject_GC(ctx);
  if (LEPUS_IsException(obj)) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  set_cycle_flag(ctx, obj);
  set_cycle_flag(ctx, this_val);
  ret =
      JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_constructor, this_val,
                                LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE);
  if (ret < 0) {
    return LEPUS_EXCEPTION;
  }
  return obj;
}

static const uint16_t func_kind_to_class_id[] = {
    [JS_FUNC_NORMAL] = JS_CLASS_BYTECODE_FUNCTION,
    [JS_FUNC_GENERATOR] = JS_CLASS_GENERATOR_FUNCTION,
    [JS_FUNC_ASYNC] = JS_CLASS_ASYNC_FUNCTION,
    [JS_FUNC_ASYNC_GENERATOR] = JS_CLASS_ASYNC_GENERATOR_FUNCTION,
};

LEPUSValue js_closure_gc(LEPUSContext *ctx, LEPUSValue bfunc,
                         JSVarRef **cur_var_refs, LEPUSStackFrame *sf) {
  LEPUSFunctionBytecode *b;
  LEPUSValue func_obj = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &func_obj, HANDLE_TYPE_LEPUS_VALUE);
  JSAtom name_atom;  // life cycle same as b
  LEPUSObject *p;
  JSProperty *pr;

  b = static_cast<LEPUSFunctionBytecode *>(LEPUS_VALUE_GET_PTR(bfunc));
  func_obj = JS_NewObjectClass_GC(ctx, func_kind_to_class_id[b->func_kind]);
  if (LEPUS_IsException(func_obj)) {
    return LEPUS_EXCEPTION;
  }
  func_obj = js_closure2(ctx, func_obj, b, cur_var_refs, sf);
  if (LEPUS_IsException(func_obj)) {
    /* bfunc has been freed */
    goto fail;
  }
  p = LEPUS_VALUE_GET_OBJ(func_obj);
  name_atom = b->func_name;
  if (name_atom == JS_ATOM_NULL) name_atom = JS_ATOM_empty_string;
  js_function_set_properties(ctx, p, name_atom, b->defined_arg_count);

  LEPUSValue proto;
  if (b->func_kind & JS_FUNC_GENERATOR) {
    int proto_class_id;
    /* generators have a prototype field which is used as
       prototype for the generator object */
    if (b->func_kind == JS_FUNC_ASYNC_GENERATOR)
      proto_class_id = JS_CLASS_ASYNC_GENERATOR;
    else
      proto_class_id = JS_CLASS_GENERATOR;
    proto = JS_NewObjectProto_GC(ctx, ctx->class_proto[proto_class_id]);
    if (LEPUS_IsException(proto)) goto fail;
    func_scope.PushHandle(&proto, HANDLE_TYPE_LEPUS_VALUE);
    pr = add_property_gc(ctx, p, JS_ATOM_prototype, LEPUS_PROP_WRITABLE);
    if (pr) pr->u.value = proto;
  } else if (b->has_prototype) {
    /* add the 'prototype' property: delay instantiation to avoid
       creating cycles for every javascript function. The prototype
       object is created on the fly when first accessed */
    LEPUS_SetConstructorBit(ctx, func_obj, TRUE);
    pr = add_property_gc(ctx, p, JS_ATOM_prototype,
                         LEPUS_PROP_AUTOINIT | LEPUS_PROP_WRITABLE);
    if (pr) {
      pr->u.init.init_func = js_instantiate_prototype;
      pr->u.init.opaque = nullptr;
    }
  }
  return func_obj;
fail:
  return LEPUS_EXCEPTION;
}

#define JS_DEFINE_CLASS_HAS_HERITAGE (1 << 0)

static void close_var_refs(LEPUSRuntime *rt, LEPUSStackFrame *sf) {
  list_head *el;
  JSVarRef *var_ref;

  list_for_each(el, &sf->var_ref_list) {
    var_ref = list_entry(el, JSVarRef, link);
    var_ref->is_detached = 1;
    var_ref->value = *var_ref->pvalue;
    var_ref->pvalue = &var_ref->value;
  }
  return;
}

QJS_STATIC LEPUSValue js_call_c_function(LEPUSContext *ctx,
                                         LEPUSValueConst func_obj,
                                         LEPUSValueConst this_obj, int argc,
                                         LEPUSValueConst *argv, int flags) {
  LEPUSRuntime *rt = ctx->rt;
  LEPUSCFunctionType func;
  LEPUSObject *p;
  LEPUSStackFrame sf_s, *sf = &sf_s, *prev_sf;
  LEPUSValue ret_val;
  LEPUSValueConst *arg_buf;
  int arg_count, i;
  LEPUSCFunctionEnum cproto;

  p = LEPUS_VALUE_GET_OBJ(func_obj);
  cproto = static_cast<LEPUSCFunctionEnum>(p->u.cfunc.cproto);
  arg_count = p->u.cfunc.length;

  /* better to always check stack overflow */
  if (js_check_stack_overflow(ctx, sizeof(arg_buf[0]) * arg_count))
    return JS_ThrowStackOverflow_GC(ctx);
#ifdef ENABLE_QUICKJS_DEBUGGER
  BOOL is_debug_mode = ctx->debugger_mode;
  if (is_debug_mode) {
    sf->pthis = this_obj;
  } else {
    sf->pthis = LEPUS_UNDEFINED;
  }
#endif
  init_list_head(&sf->var_ref_list);
  prev_sf = rt->current_stack_frame;
  sf->prev_frame = prev_sf;
  sf->js_mode = 0;
  sf->cur_func = (LEPUSValue)func_obj;
  sf->arg_count = argc;
  sf->var_buf = nullptr;
  sf->sp = nullptr;
  arg_buf = argv;
  rt->current_stack_frame = sf;

  // <Primjs begin>
  if (unlikely(argc < arg_count)) {
    /* ensure that at least argc_count arguments are readable */
#if !defined(OS_WIN)
    arg_buf = static_cast<LEPUSValue *>(alloca(sizeof(arg_buf[0]) * arg_count));
#else
    arg_buf =
        static_cast<LEPUSValue *>(_alloca(sizeof(arg_buf[0]) * arg_count));
#endif

    for (i = 0; i < argc; i++) arg_buf[i] = argv[i];
    for (i = argc; i < arg_count; i++) arg_buf[i] = LEPUS_UNDEFINED;
    sf->arg_count = arg_count;
  }

  // <Primjs end>
  sf->arg_buf = reinterpret_cast<LEPUSValue *>(arg_buf);

  func = p->u.cfunc.c_function;
  switch (cproto) {
    case LEPUS_CFUNC_constructor:
    case LEPUS_CFUNC_constructor_or_func:
      if (!(flags & LEPUS_CALL_FLAG_CONSTRUCTOR)) {
        if (cproto == LEPUS_CFUNC_constructor) {
        not_a_constructor:
          ret_val = LEPUS_ThrowTypeError(ctx, "must be called with new");
          break;
        } else {
          this_obj = LEPUS_UNDEFINED;
        }
      }
      /* here this_obj is new_target */
      /* fall thru */
    case LEPUS_CFUNC_generic:
      ret_val = func.generic(ctx, this_obj, argc, arg_buf);
      break;
    case LEPUS_CFUNC_constructor_magic:
    case LEPUS_CFUNC_constructor_or_func_magic:
      if (!(flags & LEPUS_CALL_FLAG_CONSTRUCTOR)) {
        if (cproto == LEPUS_CFUNC_constructor_magic) {
          goto not_a_constructor;
        } else {
          this_obj = LEPUS_UNDEFINED;
        }
      }
      /* fall thru */
    case LEPUS_CFUNC_generic_magic:
      ret_val =
          func.generic_magic(ctx, this_obj, argc, arg_buf, p->u.cfunc.magic);
      break;
    case LEPUS_CFUNC_getter:
      ret_val = func.getter(ctx, this_obj);
      break;
    case LEPUS_CFUNC_setter:
      ret_val = func.setter(ctx, this_obj, arg_buf[0]);
      break;
    case LEPUS_CFUNC_getter_magic:
      ret_val = func.getter_magic(ctx, this_obj, p->u.cfunc.magic);
      break;
    case LEPUS_CFUNC_setter_magic:
      ret_val = func.setter_magic(ctx, this_obj, arg_buf[0], p->u.cfunc.magic);
      break;
    case LEPUS_CFUNC_f_f: {
      double d1;

      if (unlikely(JS_ToFloat64_GC(ctx, &d1, arg_buf[0]))) {
        ret_val = LEPUS_EXCEPTION;
        break;
      }
      ret_val = LEPUS_NewFloat64(ctx, func.f_f(d1));
    } break;
    case LEPUS_CFUNC_f_f_f: {
      double d1, d2;

      if (unlikely(JS_ToFloat64_GC(ctx, &d1, arg_buf[0]))) {
        ret_val = LEPUS_EXCEPTION;
        break;
      }
      if (unlikely(JS_ToFloat64_GC(ctx, &d2, arg_buf[1]))) {
        ret_val = LEPUS_EXCEPTION;
        break;
      }
      ret_val = LEPUS_NewFloat64(ctx, func.f_f_f(d1, d2));
    } break;
    case LEPUS_CFUNC_iterator_next: {
      int done;
      ret_val = func.iterator_next(ctx, this_obj, argc, arg_buf, &done,
                                   p->u.cfunc.magic);
      if (!LEPUS_IsException(ret_val) && done != 2) {
        HandleScope func_scope(ctx, &ret_val, HANDLE_TYPE_LEPUS_VALUE);
        ret_val = js_create_iterator_result(ctx, ret_val, done);
      }
    } break;
    default:
      abort();
  }

  rt->current_stack_frame = sf->prev_frame;
  return ret_val;
}

QJS_STATIC LEPUSValue js_call_bound_function(LEPUSContext *ctx,
                                             LEPUSValueConst func_obj,
                                             LEPUSValueConst this_obj, int argc,
                                             LEPUSValueConst *argv, int flags) {
  LEPUSObject *p;
  JSBoundFunction *bf;
  LEPUSValueConst *arg_buf, new_target;
  int arg_count, i;

  p = LEPUS_VALUE_GET_OBJ(func_obj);
  bf = p->u.bound_function;
  arg_count = bf->argc + argc;
  if (js_check_stack_overflow(ctx, sizeof(LEPUSValue) * arg_count))
    return JS_ThrowStackOverflow_GC(ctx);
  // <Primjs begin>
  LEPUSValue ret;

#if !defined(OS_WIN)
  arg_buf = static_cast<LEPUSValue *>(alloca(sizeof(LEPUSValue) * arg_count));
#else
  arg_buf = static_cast<LEPUSValue *>(_alloca(sizeof(LEPUSValue) * arg_count));
#endif
  // <Primjs end>

  for (i = 0; i < bf->argc; i++) {
    arg_buf[i] = bf->argv[i];
  }
  for (i = 0; i < argc; i++) {
    arg_buf[bf->argc + i] = argv[i];
  }
  HandleScope func_scope(ctx);
  func_scope.PushLEPUSValueArrayHandle(arg_buf, arg_count, false);
  if (flags & LEPUS_CALL_FLAG_CONSTRUCTOR) {
    new_target = this_obj;
    if (js_same_value(ctx, func_obj, new_target)) new_target = bf->func_obj;
    ret = JS_CallConstructor2_GC(ctx, bf->func_obj, new_target, arg_count,
                                 arg_buf);
  } else {
    ret = JS_Call_GC(ctx, bf->func_obj, bf->this_val, arg_count, arg_buf);
  }
  return ret;
  // <Primjs end>
}

static no_inline __exception int __js_poll_interrupts(LEPUSContext *ctx) {
  LEPUSRuntime *rt = ctx->rt;
  ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT;
  if (rt->interrupt_handler) {
    if (rt->interrupt_handler(rt, rt->interrupt_opaque)) {
      /* XXX: should set a specific flag to avoid catching */
      LEPUS_ThrowInternalError(ctx, "interrupted");
      JS_SetUncatchableError(ctx, ctx->rt->current_exception, TRUE);
      return -1;
    }
  }
  return 0;
}

QJS_STATIC inline __exception int js_poll_interrupts(LEPUSContext *ctx) {
  if (unlikely(--ctx->interrupt_counter <= 0)) {
    return __js_poll_interrupts(ctx);
  } else {
    return 0;
  }
}

LEPUSValue JS_CallInternalTI_GC(LEPUSContext *caller_ctx, LEPUSValue func_obj,
                                LEPUSValue this_obj, LEPUSValue new_target,
                                int argc, LEPUSValue *argv, int flags) {
#ifdef ENABLE_PRIMJS_SNAPSHOT
  if (caller_ctx->rt->use_primjs) {
    return entry(this_obj, new_target, func_obj, (address)caller_ctx, argc,
                 argv, flags);
  }
#endif
  return LEPUS_UNDEFINED;
}

LEPUSValue JS_Call_GC(LEPUSContext *ctx, LEPUSValueConst func_obj,
                      LEPUSValueConst this_obj, int argc,
                      LEPUSValueConst *argv) {
  HandleScope func_scope(ctx);
  LEPUSValue res = JS_CallInternalTI_GC(
      ctx, func_obj, this_obj, LEPUS_UNDEFINED, argc,
      reinterpret_cast<LEPUSValue *>(argv), JS_CALL_FLAG_COPY_ARGV);
  return res;
}

LEPUSValue JS_CallFree_GC(LEPUSContext *ctx, LEPUSValue func_obj,
                          LEPUSValueConst this_obj, int argc,
                          LEPUSValueConst *argv) {
  LEPUSValue res = JS_CallInternalTI_GC(
      ctx, func_obj, this_obj, LEPUS_UNDEFINED, argc,
      reinterpret_cast<LEPUSValue *>(argv), JS_CALL_FLAG_COPY_ARGV);
  return res;
}

static LEPUSValue js_get_prototype_from_ctor(LEPUSContext *ctx,
                                             LEPUSValueConst ctor,
                                             LEPUSValueConst def_proto) {
  LEPUSValue proto;
  proto = JS_GetPropertyInternal_GC(ctx, ctor, JS_ATOM_prototype, ctor, 0);
  if (LEPUS_IsException(proto)) return proto;
  if (!LEPUS_IsObject(proto)) {
    proto = def_proto;
  }
  return proto;
}

LEPUSValue js_create_from_ctor_GC(LEPUSContext *ctx, LEPUSValueConst ctor,
                                  int class_id) {
  LEPUSValue proto, obj;
  if (LEPUS_IsUndefined(ctor)) {
    proto = ctx->class_proto[class_id];
  } else {
    proto = JS_GetPropertyInternal_GC(ctx, ctor, JS_ATOM_prototype, ctor, 0);
    if (LEPUS_IsException(proto)) return proto;
    if (!LEPUS_IsObject(proto)) {
      /* check if revoked proxy */
      {
        JSProxyData *s =
            static_cast<JSProxyData *>(LEPUS_GetOpaque(ctor, JS_CLASS_PROXY));
        if (s && s->is_revoked) return JS_ThrowTypeErrorRevokedProxy(ctx);
      }
      /* XXX: should use the ctor realm instead of 'ctx' */
      proto = ctx->class_proto[class_id];
    }
  }
  obj = JS_NewObjectProtoClass_GC(ctx, proto, class_id);
  return obj;
}

/* argv[] is modified if (flags & CALL_FLAG_COPY_ARGV) = 0. */
LEPUSValue JS_CallConstructorInternal_GC(LEPUSContext *ctx,
                                         LEPUSValueConst func_obj,
                                         LEPUSValueConst new_target, int argc,
                                         LEPUSValue *argv, int flags) {
  LEPUSObject *p;
  LEPUSFunctionBytecode *b;

  if (js_poll_interrupts(ctx)) return LEPUS_EXCEPTION;
  flags |= LEPUS_CALL_FLAG_CONSTRUCTOR;
  if (unlikely(LEPUS_VALUE_IS_NOT_OBJECT(func_obj))) goto not_a_function;
  p = LEPUS_VALUE_GET_OBJ(func_obj);
  if (unlikely(!p->is_constructor))
    return LEPUS_ThrowTypeError(ctx, "not a constructor");
  if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) {
    LEPUSClassCall *call_func;
    call_func = ctx->rt->class_array[p->class_id].call;
    if (!call_func) {
    not_a_function:
      return JS_ThrowTypeErrorNotFunction(ctx);
    }
    LEPUSValue res =
        call_func(ctx, func_obj, new_target, argc,
                  reinterpret_cast<LEPUSValueConst *>(argv), flags);
    return res;
  }

  b = p->u.func.function_bytecode;
  if (b->is_derived_class_constructor) {
    LEPUSValue res = JS_CallInternalTI_GC(ctx, func_obj, LEPUS_UNDEFINED,
                                          new_target, argc, argv, flags);
    return res;
  } else {
    LEPUSValue obj, ret;
    /* legacy constructor behavior */
    obj = js_create_from_ctor_GC(ctx, new_target, JS_CLASS_OBJECT);
    if (LEPUS_IsException(obj)) return LEPUS_EXCEPTION;
    HandleScope block_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
    ret =
        JS_CallInternalTI_GC(ctx, func_obj, obj, new_target, argc, argv, flags);
    if (LEPUS_VALUE_IS_OBJECT(ret) || LEPUS_IsException(ret)) {
      return ret;
    } else {
      return obj;
    }
  }
}

LEPUSValue JS_CallConstructor2_GC(LEPUSContext *ctx, LEPUSValueConst func_obj,
                                  LEPUSValueConst new_target, int argc,
                                  LEPUSValueConst *argv) {
  return JS_CallConstructorInternal_GC(ctx, func_obj, new_target, argc,
                                       reinterpret_cast<LEPUSValue *>(argv),
                                       JS_CALL_FLAG_COPY_ARGV);
}

LEPUSValue JS_CallConstructor_GC(LEPUSContext *ctx, LEPUSValueConst func_obj,
                                 int argc, LEPUSValueConst *argv) {
  return JS_CallConstructorInternal_GC(ctx, func_obj, func_obj, argc,
                                       reinterpret_cast<LEPUSValue *>(argv),
                                       JS_CALL_FLAG_COPY_ARGV);
}

LEPUSValue JS_Invoke_GC(LEPUSContext *ctx, LEPUSValueConst this_val,
                        JSAtom atom, int argc, LEPUSValueConst *argv) {
  LEPUSValue func_obj;
  func_obj = JS_GetPropertyInternal_GC(ctx, this_val, atom, this_val, 0);
  if (LEPUS_IsException(func_obj)) return func_obj;
  return JS_CallFree_GC(ctx, func_obj, this_val, argc, argv);
}

static LEPUSValue JS_InvokeFree(LEPUSContext *ctx, LEPUSValue this_val,
                                JSAtom atom, int argc, LEPUSValueConst *argv) {
  LEPUSValue res = JS_Invoke_GC(ctx, this_val, atom, argc, argv);
  return res;
}

/* JSAsyncFunctionState (used by generator and async functions) */
static __exception int async_func_init(LEPUSContext *ctx,
                                       JSAsyncFunctionState *s,
                                       LEPUSValueConst func_obj,
                                       LEPUSValueConst this_obj, int argc,
                                       LEPUSValueConst *argv) {
  LEPUSObject *p;
  LEPUSFunctionBytecode *b;
  LEPUSStackFrame *sf;
  int local_count, i, arg_buf_len, n;

  sf = &s->frame;
  init_list_head(&sf->var_ref_list);
  p = LEPUS_VALUE_GET_OBJ(func_obj);
  b = p->u.func.function_bytecode;
  sf->js_mode = b->js_mode;
  sf->cur_pc = b->byte_code_buf;
  arg_buf_len = max_int(b->arg_count, argc);
  local_count =
      arg_buf_len + b->var_count + b->stack_size + 10 * sizeof(char *);
  sf->arg_buf = static_cast<LEPUSValue *>(
      lepus_malloc(ctx, sizeof(LEPUSValue) * max_int(local_count, 1),
                   ALLOC_TAG_WITHOUT_PTR));
  if (!sf->arg_buf) return -1;
  sf->cur_func = func_obj;
  s->this_val = this_obj;
  s->argc = argc;
  sf->arg_count = arg_buf_len;
  sf->var_buf = sf->arg_buf + arg_buf_len;
  sf->cur_sp = sf->var_buf + b->var_count;
  sf->sp = nullptr;
  for (i = 0; i < argc; i++) sf->arg_buf[i] = argv[i];
  n = arg_buf_len + b->var_count;
  for (i = argc; i < n; i++) sf->arg_buf[i] = LEPUS_UNDEFINED;
  sf->var_refs = nullptr;
  sf->ref_size = sf->arg_count + b->var_count;
  return 0;
}

static void async_func_free(LEPUSRuntime *rt, JSAsyncFunctionState *s) {
  LEPUSStackFrame *sf;
  LEPUSValue *sp;

  sf = &s->frame;

  /* close the closure variables. */
  close_var_refs(rt, sf);
}

static LEPUSValue async_func_resume(LEPUSContext *ctx,
                                    JSAsyncFunctionState *s) {
  LEPUSValue func_obj;
  if (unlikely(js_check_stack_overflow(ctx, 0)))
    return JS_ThrowStackOverflow_GC(ctx);
  /* the tag does not matter provided it is not an object */
  func_obj = LEPUS_MKPTR(LEPUS_TAG_STRING, s);
  return JS_CallInternalTI_GC(ctx, func_obj, s->this_val, LEPUS_UNDEFINED,
                              s->argc, s->frame.arg_buf,
                              JS_CALL_FLAG_GENERATOR);
}

/* Generators */

static void free_generator_stack_rt(LEPUSRuntime *rt, JSGeneratorData *s) {
  if (s->state == JS_GENERATOR_STATE_COMPLETED) return;
  async_func_free(rt, &s->func_state);
  s->state = JS_GENERATOR_STATE_COMPLETED;
}

static void free_generator_stack(LEPUSContext *ctx, JSGeneratorData *s) {
  free_generator_stack_rt(ctx->rt, s);
}

/* XXX: use enum */
#define GEN_MAGIC_NEXT 0
#define GEN_MAGIC_RETURN 1
#define GEN_MAGIC_THROW 2

static LEPUSValue js_generator_next(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv,
                                    BOOL *pdone, int magic) {
  JSGeneratorData *s = static_cast<JSGeneratorData *>(
      LEPUS_GetOpaque(this_val, JS_CLASS_GENERATOR));
  LEPUSStackFrame *sf;
  LEPUSValue ret = LEPUS_UNDEFINED, func_ret = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &ret, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&func_ret, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst iter_args[1];

  *pdone = TRUE;
  if (!s) return LEPUS_ThrowTypeError(ctx, "not a generator");
  sf = &s->func_state.frame;
redo:
  switch (s->state) {
    default:
    case JS_GENERATOR_STATE_SUSPENDED_START:
      if (magic == GEN_MAGIC_NEXT) {
        goto exec_no_arg;
      } else {
        free_generator_stack(ctx, s);
        goto done;
      }
      break;
    case JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR: {
      int done;
      LEPUSValue method, iter_obj;
      iter_obj = sf->cur_sp[-2];
      if (magic == GEN_MAGIC_NEXT) {
        method = sf->cur_sp[-1];
      } else {
        method = JS_GetPropertyInternal_GC(
            ctx, iter_obj,
            magic == GEN_MAGIC_RETURN ? JS_ATOM_return : JS_ATOM_throw,
            iter_obj, 0);
        if (LEPUS_IsException(method)) goto iter_exception;
      }
      if (magic != GEN_MAGIC_NEXT &&
          (LEPUS_IsUndefined(method) || LEPUS_IsNull(method))) {
        /* default action */
        if (magic == GEN_MAGIC_RETURN) {
          ret = argv[0];
          goto iter_done;
        } else {
          if (JS_IteratorClose(ctx, iter_obj, FALSE)) goto iter_exception;
          LEPUS_ThrowTypeError(ctx, "iterator does not have a throw method");
          goto iter_exception;
        }
      }
      ret = JS_IteratorNext2(ctx, iter_obj, method, argc, argv, &done);
      if (LEPUS_IsException(ret)) {
      iter_exception:
        goto exec_throw;
      }
      /* if not done, the iterator returns the exact object
         returned by 'method' */
      if (done == 2) {
        LEPUSValue done_val, value;
        done_val = JS_GetPropertyInternal_GC(ctx, ret, JS_ATOM_done, ret, 0);
        if (LEPUS_IsException(done_val)) {
          goto iter_exception;
        }
        done = JS_ToBoolFree_GC(ctx, done_val);
        if (done) {
          value = JS_GetPropertyInternal_GC(ctx, ret, JS_ATOM_value, ret, 0);
          if (LEPUS_IsException(value)) goto iter_exception;
          ret = value;
          goto iter_done;
        } else {
          *pdone = 2;
        }
      } else {
        if (done) {
          /* 'yield *' returns the value associated to done = true */
        iter_done:
          sf->cur_sp--;
          goto exec_arg;
        } else {
          *pdone = FALSE;
        }
      }
      break;
    } break;
    case JS_GENERATOR_STATE_SUSPENDED_YIELD:
      /* cur_sp[-1] was set to LEPUS_UNDEFINED in the previous call */
      ret = argv[0];
      if (magic == GEN_MAGIC_THROW) {
        LEPUS_Throw(ctx, ret);
      exec_throw:
        s->func_state.throw_flag = TRUE;
      } else {
      exec_arg:
        sf->cur_sp[-1] = ret;
        sf->cur_sp[0] = LEPUS_NewBool(ctx, (magic == GEN_MAGIC_RETURN));
        sf->cur_sp++;
      exec_no_arg:
        s->func_state.throw_flag = FALSE;
      }
      s->state = JS_GENERATOR_STATE_EXECUTING;
      func_ret = async_func_resume(ctx, &s->func_state);
      s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD;
      if (LEPUS_IsException(func_ret)) {
        /* finalize the execution in case of exception */
        free_generator_stack(ctx, s);
        return func_ret;
      }
      if (LEPUS_VALUE_IS_INT(func_ret)) {
        if (LEPUS_VALUE_GET_INT(func_ret) == FUNC_RET_YIELD_STAR) {
          /* 'yield *' */
          s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR;
          iter_args[0] = LEPUS_UNDEFINED;
          argc = 1;
          argv = iter_args;
          goto redo;
        } else {
          /* get the return the yield value at the top of the stack */
          ret = sf->cur_sp[-1];
          sf->cur_sp[-1] = LEPUS_UNDEFINED;
          *pdone = FALSE;
        }
      } else {
        /* end of iterator */
        ret = sf->cur_sp[-1];
        sf->cur_sp[-1] = LEPUS_UNDEFINED;
        free_generator_stack(ctx, s);
      }
      break;
    case JS_GENERATOR_STATE_COMPLETED:
    done:
      /* execution is finished */
      switch (magic) {
        default:
        case GEN_MAGIC_NEXT:
          ret = LEPUS_UNDEFINED;
          break;
        case GEN_MAGIC_RETURN:
          ret = argv[0];
          break;
        case GEN_MAGIC_THROW:
          ret = LEPUS_Throw(ctx, argv[0]);
          break;
      }
      break;
    case JS_GENERATOR_STATE_EXECUTING:
      ret = LEPUS_ThrowTypeError(ctx, "cannot invoke a running generator");
      break;
  }
  return ret;
}

static LEPUSValue js_generator_function_call(LEPUSContext *ctx,
                                             LEPUSValueConst func_obj,
                                             LEPUSValueConst this_obj, int argc,
                                             LEPUSValueConst *argv, int flags) {
  LEPUSValue obj, func_ret;
  JSGeneratorData *s;

  s = static_cast<JSGeneratorData *>(
      lepus_mallocz(ctx, sizeof(*s), ALLOC_TAG_JSGeneratorData));
  if (!s) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, s, HANDLE_TYPE_DIR_HEAP_OBJ);
  s->state = JS_GENERATOR_STATE_SUSPENDED_START;
  if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) {
    s->state = JS_GENERATOR_STATE_COMPLETED;
    goto fail;
  }

  /* execute the function up to 'OP_initial_yield' */
  func_ret = async_func_resume(ctx, &s->func_state);
  if (LEPUS_IsException(func_ret)) goto fail;

  obj = js_create_from_ctor_GC(ctx, func_obj, JS_CLASS_GENERATOR);
  if (LEPUS_IsException(obj)) goto fail;
  LEPUS_SetOpaque(obj, s);
  return obj;
fail:
  free_generator_stack_rt(ctx->rt, s);
  return LEPUS_EXCEPTION;
}

/* AsyncFunction */

static void js_async_function_terminate(LEPUSRuntime *rt,
                                        JSAsyncFunctionData *s) {
  if (s->is_active) {
    async_func_free(rt, &s->func_state);
    s->is_active = FALSE;
  }
}

static int js_async_function_resolve_create(LEPUSContext *ctx,
                                            JSAsyncFunctionData *s,
                                            LEPUSValue *resolving_funcs) {
  int i;
  LEPUSObject *p;

  for (i = 0; i < 2; i++) {
    resolving_funcs[i] = JS_NewObjectProtoClass_GC(
        ctx, ctx->function_proto, JS_CLASS_ASYNC_FUNCTION_RESOLVE + i);
    if (LEPUS_IsException(resolving_funcs[i])) {
      return -1;
    }
    p = LEPUS_VALUE_GET_OBJ(resolving_funcs[i]);
    p->u.async_function_data = s;
  }
  return 0;
}

static void js_async_function_resume(LEPUSContext *ctx,
                                     JSAsyncFunctionData *s) {
  LEPUSValue func_ret, ret2;
  func_ret = async_func_resume(ctx, &s->func_state);
  if (LEPUS_IsException(func_ret)) {
    LEPUSValue error;
  fail:
    error = LEPUS_GetException(ctx);
    ret2 = JS_Call_GC(ctx, s->resolving_funcs[1], LEPUS_UNDEFINED, 1,
                      reinterpret_cast<LEPUSValueConst *>(&error));
    js_async_function_terminate(ctx->rt, s);
  } else {
    HandleScope block_scope(ctx->rt);
    LEPUSValue value;
    value = s->func_state.frame.cur_sp[-1];
    block_scope.PushHandle(&value, HANDLE_TYPE_LEPUS_VALUE);
    s->func_state.frame.cur_sp[-1] = LEPUS_UNDEFINED;
    if (LEPUS_IsUndefined(func_ret)) {
      /* function returned */
      ret2 = JS_Call_GC(ctx, s->resolving_funcs[0], LEPUS_UNDEFINED, 1,
                        reinterpret_cast<LEPUSValueConst *>(&value));
      js_async_function_terminate(ctx->rt, s);
    } else {
      HandleScope block_scope2(ctx->rt);
      LEPUSValue promise, resolving_funcs[2], resolving_funcs1[2];
      block_scope2.PushLEPUSValueArrayHandle(resolving_funcs, 2);
      block_scope2.PushLEPUSValueArrayHandle(resolving_funcs1, 2);
      int i, res;

      /* await */
      promise =
          js_promise_resolve(ctx, ctx->promise_ctor, 1,
                             reinterpret_cast<LEPUSValueConst *>(&value), 0);
      if (LEPUS_IsException(promise)) goto fail;
      block_scope2.PushHandle(&promise, HANDLE_TYPE_LEPUS_VALUE);
      if (js_async_function_resolve_create(ctx, s, resolving_funcs)) {
        goto fail;
      }

      /* Note: no need to create 'thrownawayCapability' as in
         the spec */
      res = perform_promise_then(
          ctx, promise, reinterpret_cast<LEPUSValueConst *>(resolving_funcs),
          reinterpret_cast<LEPUSValueConst *>(resolving_funcs1));
      if (res) goto fail;
    }
  }
}

static LEPUSValue js_async_function_resolve_call(
    LEPUSContext *ctx, LEPUSValueConst func_obj, LEPUSValueConst this_obj,
    int argc, LEPUSValueConst *argv, int flags) {
  LEPUSObject *p = LEPUS_VALUE_GET_OBJ(func_obj);
  JSAsyncFunctionData *s = p->u.async_function_data;
  BOOL is_reject = p->class_id - JS_CLASS_ASYNC_FUNCTION_RESOLVE;
  LEPUSValueConst arg;

  if (argc > 0)
    arg = argv[0];
  else
    arg = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &arg, HANDLE_TYPE_LEPUS_VALUE);
  s->func_state.throw_flag = is_reject;
  if (is_reject) {
    LEPUS_Throw(ctx, arg);
  } else {
    /* return value of await */
    s->func_state.frame.cur_sp[-1] = arg;
  }
  js_async_function_resume(ctx, s);
  return LEPUS_UNDEFINED;
}

static LEPUSValue js_async_function_call(LEPUSContext *ctx,
                                         LEPUSValueConst func_obj,
                                         LEPUSValueConst this_obj, int argc,
                                         LEPUSValueConst *argv, int flags) {
  LEPUSValue promise;
  JSAsyncFunctionData *s;

  s = static_cast<JSAsyncFunctionData *>(
      lepus_mallocz(ctx, sizeof(*s), ALLOC_TAG_JSAsyncFunctionData));
  if (!s) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, s, HANDLE_TYPE_DIR_HEAP_OBJ);
  s->is_active = FALSE;
  s->resolving_funcs[0] = LEPUS_UNDEFINED;
  s->resolving_funcs[1] = LEPUS_UNDEFINED;

  promise = JS_NewPromiseCapability_GC(ctx, s->resolving_funcs);
  if (LEPUS_IsException(promise)) goto fail;
  func_scope.PushHandle(&promise, HANDLE_TYPE_LEPUS_VALUE);

  if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) {
  fail:
    return LEPUS_EXCEPTION;
  }
  s->is_active = TRUE;

  js_async_function_resume(ctx, s);
  return promise;
}

/* AsyncGenerator */

typedef enum JSAsyncGeneratorStateEnum {
  JS_ASYNC_GENERATOR_STATE_SUSPENDED_START,
  JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD,
  JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR,
  JS_ASYNC_GENERATOR_STATE_EXECUTING,
  JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN,
  JS_ASYNC_GENERATOR_STATE_COMPLETED,
} JSAsyncGeneratorStateEnum;

typedef struct JSAsyncGeneratorRequest {
  struct list_head link;
  /* completion */
  int completion_type; /* GEN_MAGIC_x */
  LEPUSValue result;
  /* promise capability */
  LEPUSValue promise;
  LEPUSValue resolving_funcs[2];
} JSAsyncGeneratorRequest;

typedef struct JSAsyncGeneratorData {
  LEPUSObject *generator; /* back pointer to the object (const) */
  JSAsyncGeneratorStateEnum state;
  JSAsyncFunctionState func_state;
  struct list_head queue; /* list of JSAsyncGeneratorRequest.link */
} JSAsyncGeneratorData;

static LEPUSValue js_async_generator_resolve_function(
    LEPUSContext *ctx, LEPUSValueConst this_obj, int argc,
    LEPUSValueConst *argv, int magic, LEPUSValue *func_data);

static int js_async_generator_resolve_function_create(
    LEPUSContext *ctx, LEPUSValueConst generator, LEPUSValue *resolving_funcs,
    BOOL is_resume_next) {
  int i;
  LEPUSValue func;

  for (i = 0; i < 2; i++) {
    func = JS_NewCFunctionData_GC(ctx, js_async_generator_resolve_function, 1,
                                  i + is_resume_next * 2, 1, &generator);
    if (LEPUS_IsException(func)) {
      return -1;
    }
    resolving_funcs[i] = func;
  }
  return 0;
}

static int js_async_generator_await(LEPUSContext *ctx, JSAsyncGeneratorData *s,
                                    LEPUSValueConst value) {
  HandleScope func_scope(ctx);
  LEPUSValue promise, resolving_funcs[2], resolving_funcs1[2];
  func_scope.PushLEPUSValueArrayHandle(resolving_funcs, 2);
  func_scope.PushLEPUSValueArrayHandle(resolving_funcs1, 2);
  int i, res;

  promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, &value, 0);
  if (LEPUS_IsException(promise)) goto fail;
  func_scope.PushHandle(&promise, HANDLE_TYPE_LEPUS_VALUE);

  if (js_async_generator_resolve_function_create(
          ctx, LEPUS_MKPTR(LEPUS_TAG_OBJECT, s->generator), resolving_funcs,
          FALSE)) {
    goto fail;
  }

  /* Note: no need to create 'thrownawayCapability' as in
     the spec */
  res = perform_promise_then(
      ctx, promise, reinterpret_cast<LEPUSValueConst *>(resolving_funcs),
      reinterpret_cast<LEPUSValueConst *>(resolving_funcs1));
  if (res) goto fail;
  return 0;
fail:
  return -1;
}

static void js_async_generator_resolve_or_reject(LEPUSContext *ctx,
                                                 JSAsyncGeneratorData *s,
                                                 LEPUSValueConst result,
                                                 int is_reject) {
  JSAsyncGeneratorRequest *next;
  LEPUSValue ret;

  next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link);
  list_del(&next->link);
  ret = JS_Call_GC(ctx, next->resolving_funcs[is_reject], LEPUS_UNDEFINED, 1,
                   &result);
}

static void js_async_generator_resolve(LEPUSContext *ctx,
                                       JSAsyncGeneratorData *s,
                                       LEPUSValueConst value, BOOL done) {
  LEPUSValue result;
  result = js_create_iterator_result(ctx, value, done);
  HandleScope func_scope(ctx, &result, HANDLE_TYPE_LEPUS_VALUE);
  /* XXX: better exception handling ? */
  js_async_generator_resolve_or_reject(ctx, s, result, 0);
}

static void js_async_generator_reject(LEPUSContext *ctx,
                                      JSAsyncGeneratorData *s,
                                      LEPUSValueConst exception) {
  js_async_generator_resolve_or_reject(ctx, s, exception, 1);
}

static void js_async_generator_complete(LEPUSContext *ctx,
                                        JSAsyncGeneratorData *s) {
  if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED) {
    s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED;
    async_func_free(ctx->rt, &s->func_state);
  }
}

static int js_async_generator_completed_return(LEPUSContext *ctx,
                                               JSAsyncGeneratorData *s,
                                               LEPUSValueConst value) {
  HandleScope func_scope(ctx);
  LEPUSValue promise, resolving_funcs[2], resolving_funcs1[2];
  func_scope.PushLEPUSValueArrayHandle(resolving_funcs, 2);
  func_scope.PushLEPUSValueArrayHandle(resolving_funcs1, 2);
  int res;

  promise = js_promise_resolve(ctx, ctx->promise_ctor, 1,
                               reinterpret_cast<LEPUSValueConst *>(&value), 0);
  if (LEPUS_IsException(promise)) return -1;
  func_scope.PushHandle(&promise, HANDLE_TYPE_LEPUS_VALUE);
  if (js_async_generator_resolve_function_create(
          ctx, LEPUS_MKPTR(LEPUS_TAG_OBJECT, s->generator), resolving_funcs1,
          TRUE)) {
    return -1;
  }
  res = perform_promise_then(
      ctx, promise, reinterpret_cast<LEPUSValueConst *>(resolving_funcs1),
      reinterpret_cast<LEPUSValueConst *>(resolving_funcs));
  return res;
}

static void js_async_generator_resume_next(LEPUSContext *ctx,
                                           JSAsyncGeneratorData *s) {
  HandleScope func_scope(ctx);
  JSAsyncGeneratorRequest *next;
  LEPUSValue func_ret = LEPUS_UNDEFINED, value = LEPUS_UNDEFINED;
  func_scope.PushHandle(&func_ret, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&value, HANDLE_TYPE_LEPUS_VALUE);

  for (;;) {
    if (list_empty(&s->queue)) break;
    next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link);
    switch (s->state) {
      case JS_ASYNC_GENERATOR_STATE_EXECUTING:
        /* only happens when restarting execution after await() */
        goto resume_exec;
      case JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN:
        goto done;
      case JS_ASYNC_GENERATOR_STATE_SUSPENDED_START:
        if (next->completion_type == GEN_MAGIC_NEXT) {
          goto exec_no_arg;
        } else {
          js_async_generator_complete(ctx, s);
        }
        break;
      case JS_ASYNC_GENERATOR_STATE_COMPLETED:
        if (next->completion_type == GEN_MAGIC_NEXT) {
          js_async_generator_resolve(ctx, s, LEPUS_UNDEFINED, TRUE);
        } else if (next->completion_type == GEN_MAGIC_RETURN) {
          s->state = JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN;
          js_async_generator_completed_return(ctx, s, next->result);
          goto done;
        } else {
          js_async_generator_reject(ctx, s, next->result);
        }
        goto done;
      case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD:
      case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR:
        value = next->result;
        if (next->completion_type == GEN_MAGIC_THROW &&
            s->state == JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD) {
          LEPUS_Throw(ctx, value);
          s->func_state.throw_flag = TRUE;
        } else {
          /* 'yield' returns a value. 'yield *' also returns a value
             in case the 'throw' method is called */
          s->func_state.frame.cur_sp[-1] = value;
          s->func_state.frame.cur_sp[0] =
              LEPUS_NewInt32(ctx, next->completion_type);
          s->func_state.frame.cur_sp++;
        exec_no_arg:
          s->func_state.throw_flag = FALSE;
        }
        s->state = JS_ASYNC_GENERATOR_STATE_EXECUTING;
      resume_exec:
        func_ret = async_func_resume(ctx, &s->func_state);
        if (LEPUS_IsException(func_ret)) {
          value = LEPUS_GetException(ctx);
          js_async_generator_complete(ctx, s);
          js_async_generator_reject(ctx, s, value);
        } else if (LEPUS_VALUE_IS_INT(func_ret)) {
          int func_ret_code;
          value = s->func_state.frame.cur_sp[-1];
          s->func_state.frame.cur_sp[-1] = LEPUS_UNDEFINED;
          func_ret_code = LEPUS_VALUE_GET_INT(func_ret);
          switch (func_ret_code) {
            case FUNC_RET_YIELD:
            case FUNC_RET_YIELD_STAR:
              if (func_ret_code == FUNC_RET_YIELD_STAR)
                s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR;
              else
                s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD;
              js_async_generator_resolve(ctx, s, value, FALSE);
              break;
            case FUNC_RET_AWAIT:
              js_async_generator_await(ctx, s, value);
              goto done;
            default:
              abort();
          }
        } else {
          assert(LEPUS_IsUndefined(func_ret));
          /* end of function */
          value = s->func_state.frame.cur_sp[-1];
          s->func_state.frame.cur_sp[-1] = LEPUS_UNDEFINED;
          js_async_generator_complete(ctx, s);
          js_async_generator_resolve(ctx, s, value, TRUE);
        }
        break;
      default:
        abort();
    }
  }
done: {}
}

static LEPUSValue js_async_generator_resolve_function(
    LEPUSContext *ctx, LEPUSValueConst this_obj, int argc,
    LEPUSValueConst *argv, int magic, LEPUSValue *func_data) {
  BOOL is_reject = magic & 1;
  JSAsyncGeneratorData *s = static_cast<JSAsyncGeneratorData *>(
      LEPUS_GetOpaque(func_data[0], JS_CLASS_ASYNC_GENERATOR));
  LEPUSValueConst arg = argv[0];
  HandleScope func_scope(ctx, &arg, HANDLE_TYPE_LEPUS_VALUE);

  /* XXX: what if s == NULL */

  if (magic >= 2) {
    /* resume next case in AWAITING_RETURN state */
    assert(s->state == JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN ||
           s->state == JS_ASYNC_GENERATOR_STATE_COMPLETED);
    s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED;
    if (is_reject) {
      js_async_generator_reject(ctx, s, arg);
    } else {
      js_async_generator_resolve(ctx, s, arg, TRUE);
    }
  } else {
    /* restart function execution after await() */
    assert(s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING);
    s->func_state.throw_flag = is_reject;
    if (is_reject) {
      LEPUS_Throw(ctx, arg);
    } else {
      /* return value of await */
      s->func_state.frame.cur_sp[-1] = arg;
    }
    js_async_generator_resume_next(ctx, s);
  }
  return LEPUS_UNDEFINED;
}

/* magic = GEN_MAGIC_x */
static LEPUSValue js_async_generator_next(LEPUSContext *ctx,
                                          LEPUSValueConst this_val, int argc,
                                          LEPUSValueConst *argv, int magic) {
  HandleScope func_scope(ctx);
  JSAsyncGeneratorData *s = static_cast<JSAsyncGeneratorData *>(
      LEPUS_GetOpaque(this_val, JS_CLASS_ASYNC_GENERATOR));
  LEPUSValue promise, resolving_funcs[2];
  func_scope.PushLEPUSValueArrayHandle(resolving_funcs, 2);
  JSAsyncGeneratorRequest *req;

  promise = JS_NewPromiseCapability_GC(ctx, resolving_funcs);
  if (LEPUS_IsException(promise)) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&promise, HANDLE_TYPE_LEPUS_VALUE);
  if (!s) {
    HandleScope block_scope(ctx->rt);
    LEPUSValue err, res2;
    LEPUS_ThrowTypeError(ctx, "not an AsyncGenerator object");
    err = LEPUS_GetException(ctx);
    func_scope.PushHandle(&err, HANDLE_TYPE_LEPUS_VALUE);
    res2 = JS_Call_GC(ctx, resolving_funcs[1], LEPUS_UNDEFINED, 1,
                      reinterpret_cast<LEPUSValueConst *>(&err));
    return promise;
  }
  req = static_cast<JSAsyncGeneratorRequest *>(
      lepus_mallocz(ctx, sizeof(*req), ALLOC_TAG_WITHOUT_PTR));
  if (!req) goto fail;
  req->completion_type = magic;
  req->result = argv[0];
  req->promise = promise;
  req->resolving_funcs[0] = resolving_funcs[0];
  req->resolving_funcs[1] = resolving_funcs[1];
  list_add_tail(&req->link, &s->queue);
  if (s->state != JS_ASYNC_GENERATOR_STATE_EXECUTING) {
    js_async_generator_resume_next(ctx, s);
  }
  return promise;
fail:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_async_generator_function_call(
    LEPUSContext *ctx, LEPUSValueConst func_obj, LEPUSValueConst this_obj,
    int argc, LEPUSValueConst *argv, int flags) {
  LEPUSValue obj, func_ret;
  JSAsyncGeneratorData *s;

  s = static_cast<JSAsyncGeneratorData *>(
      lepus_mallocz(ctx, sizeof(*s), ALLOC_TAG_JSAsyncGeneratorData));
  HandleScope func_scope(ctx, s, HANDLE_TYPE_DIR_HEAP_OBJ);
  if (!s) return LEPUS_EXCEPTION;
  s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_START;
  init_list_head(&s->queue);
  if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) {
    s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED;
    goto fail;
  }

  /* execute the function up to 'OP_initial_yield' (no yield nor
     await are possible) */
  func_ret = async_func_resume(ctx, &s->func_state);
  if (LEPUS_IsException(func_ret)) goto fail;

  obj = js_create_from_ctor_GC(ctx, func_obj, JS_CLASS_ASYNC_GENERATOR);
  if (LEPUS_IsException(obj)) goto fail;
  s->generator = LEPUS_VALUE_GET_OBJ(obj);
  LEPUS_SetOpaque(obj, s);
  return obj;
fail:
  return LEPUS_EXCEPTION;
}

/* LEPUS parser */

/* unicode code points */
#define CP_NBSP 0x00a0
#define CP_BOM 0xfeff

#define CP_LS 0x2028
#define CP_PS 0x2029

#ifndef NO_QUICKJS_COMPILER
// <Primjs end>

#ifdef ENABLE_QUICKJS_DEBUGGER
static void js_gen_debugger_statement(JSParseState *s, LEPUSContext *ctx);
#endif

static void emit_u8(JSParseState *s, uint8_t val) {
  dbuf_putc(&s->cur_func->byte_code, val);
}

static void emit_u16(JSParseState *s, uint16_t val) {
  dbuf_put_u16(&s->cur_func->byte_code, val);
}

static void emit_u32(JSParseState *s, uint32_t val) {
  dbuf_put_u32(&s->cur_func->byte_code, val);
}

static void emit_atom(JSParseState *s, JSAtom name) { emit_u32(s, name); }

static int new_label(JSParseState *s) { return new_label_fd(s->cur_func, -1); }

int define_var_GC(JSParseState *s, JSFunctionDef *fd, JSAtom name,
                  JSVarDefEnum var_def_type) {
  LEPUSContext *ctx = s->ctx;
  JSVarDef *vd;
  int idx;

  switch (var_def_type) {
    case JS_VAR_DEF_WITH:
      idx = add_scope_var(ctx, fd, name, JS_VAR_NORMAL);
      break;

    case JS_VAR_DEF_LET:
    case JS_VAR_DEF_CONST:
    case JS_VAR_DEF_FUNCTION_DECL:
    case JS_VAR_DEF_NEW_FUNCTION_DECL:
      idx = find_lexical_decl(ctx, fd, name, fd->scope_first, TRUE);
      if (idx >= 0) {
        if (idx < GLOBAL_VAR_OFFSET) {
          if (fd->vars[idx].scope_level == fd->scope_level) {
            /* same scope: in non strict mode, functions can be redefined
             * (annex B.3.3.4). */
            if (!(!(fd->js_mode & JS_MODE_STRICT) &&
                  var_def_type == JS_VAR_DEF_FUNCTION_DECL &&
                  fd->vars[idx].var_kind == JS_VAR_FUNCTION_DECL)) {
              goto redef_lex_error;
            }
          } else if (fd->vars[idx].var_kind == JS_VAR_CATCH &&
                     (fd->vars[idx].scope_level + 2) == fd->scope_level) {
            goto redef_lex_error;
          }
        } else {
          if (fd->scope_level == fd->body_scope) {
          redef_lex_error:
            /* redefining a scoped var in the same scope: error */
            return js_parse_error(s,
                                  "invalid redefinition of lexical identifier");
          }
        }
      }
      if (var_def_type != JS_VAR_DEF_FUNCTION_DECL &&
          var_def_type != JS_VAR_DEF_NEW_FUNCTION_DECL &&
          fd->scope_level == fd->body_scope && find_arg(ctx, fd, name) >= 0) {
        /* lexical variable redefines a parameter name */
        return js_parse_error(s, "invalid redefinition of parameter name");
      }

      if (find_var_in_child_scope(ctx, fd, name, fd->scope_level) >= 0) {
        return js_parse_error(s, "invalid redefinition of a variable");
      }

      if (fd->is_global_var) {
        JSHoistedDef *hf;
        hf = find_hoisted_def(fd, name);
        /* XXX: should check scope chain */
        if (hf && is_child_scope(ctx, fd, hf->scope_level, fd->scope_level)) {
          return js_parse_error(s, "invalid redefinition of global identifier");
        }
      }

      if (fd->is_eval &&
          (fd->eval_type == LEPUS_EVAL_TYPE_GLOBAL ||
           fd->eval_type == LEPUS_EVAL_TYPE_MODULE) &&
          fd->scope_level == fd->body_scope) {
        JSHoistedDef *hf;
        hf = add_hoisted_def(s->ctx, fd, -1, name, -1, TRUE);
        if (!hf) return -1;
        hf->is_lexical = TRUE;
        hf->is_const = (var_def_type == JS_VAR_DEF_CONST);
        idx = GLOBAL_VAR_OFFSET;
      } else {
        JSVarKindEnum var_kind;
        if (var_def_type == JS_VAR_DEF_FUNCTION_DECL)
          var_kind = JS_VAR_FUNCTION_DECL;
        else if (var_def_type == JS_VAR_DEF_NEW_FUNCTION_DECL)
          var_kind = JS_VAR_NEW_FUNCTION_DECL;
        else
          var_kind = JS_VAR_NORMAL;
        idx = add_scope_var(ctx, fd, name, var_kind);
        if (idx >= 0) {
          vd = &fd->vars[idx];
          vd->is_lexical = 1;
          vd->is_const = (var_def_type == JS_VAR_DEF_CONST);
        }
      }
      break;

    case JS_VAR_DEF_CATCH:
      idx = add_scope_var(ctx, fd, name, JS_VAR_CATCH);
      break;

    case JS_VAR_DEF_VAR:
      if (find_lexical_decl(ctx, fd, name, fd->scope_first, FALSE) >= 0) {
      invalid_lexical_redefinition:
        /* error to redefine a var that inside a lexical scope */
        return js_parse_error(s, "invalid redefinition of lexical identifier");
      }
      if (fd->is_global_var) {
        JSHoistedDef *hf;
        hf = find_hoisted_def(fd, name);
        if (hf && hf->is_lexical && hf->scope_level == fd->scope_level &&
            fd->eval_type == LEPUS_EVAL_TYPE_MODULE) {
          goto invalid_lexical_redefinition;
        }
        hf = add_hoisted_def(s->ctx, fd, -1, name, -1, FALSE);
        if (!hf) return -1;
        idx = GLOBAL_VAR_OFFSET;
      } else {
        /* if the variable already exists, don't add it again  */
        idx = find_var(ctx, fd, name);
        if (idx >= 0) break;
        idx = add_var(ctx, fd, name);
        if (idx >= 0) {
          if (name == JS_ATOM_arguments && fd->has_arguments_binding)
            fd->arguments_var_idx = idx;
          fd->vars[idx].scope_next = fd->scope_level;
        }
      }
      break;
    default:
      abort();
  }
  return idx;
}

static __exception int js_parse_function_decl(JSParseState *s,
                                              JSParseFunctionEnum func_type,
                                              JSFunctionKindEnum func_kind,
                                              JSAtom func_name,
                                              const uint8_t *ptr,
                                              int start_line);
static void push_break_entry(JSFunctionDef *fd, BlockEnv *be, JSAtom label_name,
                             int label_break, int label_cont, int drop_count);
static void pop_break_entry(JSFunctionDef *fd);

/* Note: all the fields are already sealed except length */
int seal_template_obj_GC(LEPUSContext *ctx, LEPUSValueConst obj) {
  LEPUSObject *p;
  JSShapeProperty *prs;

  p = LEPUS_VALUE_GET_OBJ(obj);
  prs = find_own_property1(p, JS_ATOM_length);
  if (prs) {
    if (js_update_property_flags(
            ctx, p, &prs,
            prs->flags & ~(LEPUS_PROP_CONFIGURABLE | LEPUS_PROP_WRITABLE)))
      return -1;
  }
  p->extensible = FALSE;
  return 0;
}

/* allow the 'in' binary operator */
#define PF_IN_ACCEPTED (1 << 0)
/* allow function calls parsing in js_parse_postfix_expr() */
#define PF_POSTFIX_CALL (1 << 1)
/* allow arrow functions parsing in js_parse_postfix_expr() */
#define PF_ARROW_FUNC (1 << 2)
/* allow the exponentiation operator in js_parse_unary() */
#define PF_POW_ALLOWED (1 << 3)
/* forbid the exponentiation operator in js_parse_unary() */
#define PF_POW_FORBIDDEN (1 << 4)
#define PF_LASTEST_ISNEW (1 << 5)

#define PROP_TYPE_IDENT 0
#define PROP_TYPE_VAR 1
#define PROP_TYPE_GET 2
#define PROP_TYPE_SET 3
#define PROP_TYPE_STAR 4
#define PROP_TYPE_ASYNC 5
#define PROP_TYPE_ASYNC_STAR 6

#define PROP_TYPE_PRIVATE (1 << 4)

/* if the property is an expression, name = JS_ATOM_NULL */
int __exception js_parse_property_name_GC(JSParseState *s, JSAtom *pname,
                                          BOOL allow_method, BOOL allow_var,
                                          BOOL allow_private) {
  int is_private = 0;
  JSAtom name;
  int prop_type;

  prop_type = PROP_TYPE_IDENT;
  if (allow_method) {
    if ((token_is_pseudo_keyword(s, JS_ATOM_get) ||
         token_is_pseudo_keyword(s, JS_ATOM_set)) &&
        peek_token(s, FALSE) != ',' && peek_token(s, FALSE) != '}') {
      /* get x(), set x() */
      name = s->token.u.ident.atom;
      if (next_token(s)) goto fail1;
      if (s->token.val == ':' || s->token.val == ',' || s->token.val == '}' ||
          s->token.val == '(')
        goto done;
      prop_type = PROP_TYPE_GET + (name == JS_ATOM_set);
    } else if (s->token.val == '*') {
      if (next_token(s)) goto fail;
      prop_type = PROP_TYPE_STAR;
    } else if (token_is_pseudo_keyword(s, JS_ATOM_async) &&
               peek_token(s, TRUE) != '\n') {
      name = s->token.u.ident.atom;
      if (next_token(s)) goto fail1;
      if (s->token.val == '}' || s->token.val == ',') {
        prop_type = PROP_TYPE_VAR;
        goto done;
      }
      if (s->token.val == ':' || s->token.val == '(') goto done;
      if (s->token.val == '*') {
        if (next_token(s)) goto fail;
        prop_type = PROP_TYPE_ASYNC_STAR;
      } else {
        prop_type = PROP_TYPE_ASYNC;
      }
    }
  }

  if (token_is_ident(s->token.val)) {
    /* variable can only be a non-reserved identifier */
    if (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved &&
        prop_type == PROP_TYPE_IDENT && allow_var) {
      int tok = peek_token(s, FALSE);
      if (!(tok == ':' || (tok == '(' && allow_method))) {
        prop_type = PROP_TYPE_VAR;
      }
    }
    /* keywords and reserved words have a valid atom */
    name = s->token.u.ident.atom;
    if (next_token(s)) goto fail1;
  } else if (s->token.val == TOK_STRING) {
    name = js_value_to_atom_gc(s->ctx, s->token.u.str.str);
    if (name == JS_ATOM_NULL) goto fail;
    if (next_token(s)) goto fail1;
  } else if (s->token.val == TOK_NUMBER) {
    LEPUSValue val;
    val = s->token.u.num.val;
    HandleScope block_scope(s->ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
    name = js_value_to_atom_gc(s->ctx, val);
    if (name == JS_ATOM_NULL) goto fail;
    if (next_token(s)) goto fail1;
  } else if (s->token.val == '[') {
    if (next_token(s)) goto fail;
    if (js_parse_expr(s)) goto fail;
    if (js_parse_expect(s, ']')) goto fail;
    name = JS_ATOM_NULL;
  } else if (s->token.val == TOK_PRIVATE_NAME && allow_private) {
    name = s->token.u.ident.atom;
    if (next_token(s)) goto fail1;
    is_private = PROP_TYPE_PRIVATE;
  } else {
    goto invalid_prop;
  }
  if (prop_type != PROP_TYPE_IDENT && prop_type != PROP_TYPE_VAR &&
      s->token.val != '(') {
  invalid_prop:
    js_parse_error(s, "invalid property name");
    goto fail;
  }
done:
  *pname = name;
  return prop_type | is_private;
fail1:
fail:
  *pname = JS_ATOM_NULL;
  return -1;
}

typedef struct JSParsePos {
  int last_line_num;
  int line_num;
  BOOL got_lf;
  const uint8_t *ptr;
  // <Primjs begin>
  const uint8_t *last_line_begin_ptr;
  const uint8_t *line_begin_ptr;
  const uint8_t *utf8_parse_front;
  int utf8_adapte_size;
  int last_utf8_adapte_size;
  // <Primjs end>
} JSParsePos;

#define SKIP_HAS_SEMI (1 << 0)
#define SKIP_HAS_ELLIPSIS (1 << 1)
#define SKIP_HAS_ASSIGNMENT (1 << 2)
__exception int js_parse_object_literal_GC(JSParseState *s) {
  JSAtom name = JS_ATOM_NULL;
  const uint8_t *start_ptr;
  int start_line, prop_type;
  BOOL has_proto;
  HandleScope func_scope(s->ctx->rt);

  if (next_token(s)) goto fail;
  /* XXX: add an initial length that will be patched back */
  emit_op(s, OP_object);
  has_proto = FALSE;
  while (s->token.val != '}') {
    /* specific case for getter/setter */
    start_ptr = s->token.ptr;
    start_line = s->token.line_num;

    if (s->token.val == TOK_ELLIPSIS) {
      if (next_token(s)) return -1;
      if (js_parse_assign_expr(s, PF_IN_ACCEPTED)) return -1;
      emit_op(s, OP_null); /* dummy excludeList */
      emit_op(s, OP_copy_data_properties);
      emit_u8(s, 2 | (1 << 2) | (0 << 5));
      emit_op(s, OP_drop); /* pop excludeList */
      emit_op(s, OP_drop); /* pop src object */
      goto next;
    }

    prop_type = js_parse_property_name_GC(s, &name, TRUE, TRUE, FALSE);

    func_scope.PushLEPUSAtom(name);
    if (prop_type < 0) goto fail;

    if (prop_type == PROP_TYPE_VAR) {
      /* shortcut for x: x */
      emit_op(s, OP_scope_get_var);
      emit_atom(s, name);
      emit_u16(s, s->cur_func->scope_level);
      emit_op(s, OP_define_field);
      emit_atom(s, name);
    } else if (s->token.val == '(') {
      BOOL is_getset =
          (prop_type == PROP_TYPE_GET || prop_type == PROP_TYPE_SET);
      JSParseFunctionEnum func_type;
      JSFunctionKindEnum func_kind;
      int op_flags;

      func_kind = JS_FUNC_NORMAL;
      if (is_getset) {
        func_type = static_cast<JSParseFunctionEnum>(JS_PARSE_FUNC_GETTER +
                                                     prop_type - PROP_TYPE_GET);
      } else {
        func_type = JS_PARSE_FUNC_METHOD;
        if (prop_type == PROP_TYPE_STAR)
          func_kind = JS_FUNC_GENERATOR;
        else if (prop_type == PROP_TYPE_ASYNC)
          func_kind = JS_FUNC_ASYNC;
        else if (prop_type == PROP_TYPE_ASYNC_STAR)
          func_kind = JS_FUNC_ASYNC_GENERATOR;
      }
      if (js_parse_function_decl(s, func_type, func_kind, JS_ATOM_NULL,
                                 start_ptr, start_line))
        goto fail;
      if (name == JS_ATOM_NULL) {
        emit_op(s, OP_define_method_computed);
      } else {
        emit_op(s, OP_define_method);
        emit_atom(s, name);
      }
      if (is_getset) {
        op_flags = OP_DEFINE_METHOD_GETTER + prop_type - PROP_TYPE_GET;
      } else {
        op_flags = OP_DEFINE_METHOD_METHOD;
      }
      emit_u8(s, op_flags | OP_DEFINE_METHOD_ENUMERABLE);
    } else {
      if (js_parse_expect(s, ':')) goto fail;
      if (js_parse_assign_expr(s, PF_IN_ACCEPTED)) goto fail;
      if (name == JS_ATOM_NULL) {
        set_object_name_computed(s);
        emit_op(s, OP_define_array_el);
        emit_op(s, OP_drop);
      } else if (name == JS_ATOM___proto__) {
        if (has_proto) {
          js_parse_error(s, "duplicate __proto__ property name");
          goto fail;
        }
        emit_op(s, OP_set_proto);
        has_proto = TRUE;
      } else {
        set_object_name(s, name);
        emit_op(s, OP_define_field);
        emit_atom(s, name);
      }
    }
  next:
    name = JS_ATOM_NULL;
    if (s->token.val != ',') break;
    if (next_token(s)) goto fail;
  }
  if (js_parse_expect(s, '}')) goto fail;
  return 0;
fail:
  return -1;
}

static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags);

__exception int js_parse_left_hand_side_expr_GC(JSParseState *s) {
  return js_parse_postfix_expr(s, PF_POSTFIX_CALL);
}

/* initialize the class fields, called by the constructor. Note:
   super() can be called in an arrow function, so <this> and
   <class_fields_init> can be variable references */
static void emit_class_field_init(JSParseState *s) {
  int label_next;

  emit_op(s, OP_scope_get_var);
  emit_atom(s, JS_ATOM_class_fields_init);
  emit_u16(s, s->cur_func->scope_level);

  /* no need to call the class field initializer if not defined */
  emit_op(s, OP_dup);
  label_next = emit_goto(s, OP_if_false, -1);

  emit_op(s, OP_scope_get_var);
  emit_atom(s, JS_ATOM_this);
  emit_u16(s, 0);

  emit_op(s, OP_swap);

  emit_op(s, OP_call_method);
  emit_u16(s, 0);

  emit_label(s, label_next);
  emit_op(s, OP_drop);
}

typedef struct {
  JSFunctionDef *fields_init_fd;
  int computed_fields_count;
  BOOL has_brand;
  int brand_push_pos;
} ClassFieldsDef;

/* XXX: remove */
static BOOL has_with_scope(JSFunctionDef *s, int scope_level) {
  /* check if scope chain contains a with statement */
  while (s) {
    int scope_idx = s->scopes[scope_level].first;
    while (scope_idx >= 0) {
      JSVarDef *vd = &s->vars[scope_idx];

      if (vd->var_name == JS_ATOM__with_) return TRUE;
      scope_idx = vd->scope_next;
    }
    /* check parent scopes */
    scope_level = s->parent_scope_level;
    s = s->parent;
  }
  return FALSE;
}

typedef struct JSLValue {
  int opcode;
  int scope;
  int label;
  int depth;
  int tok;
  JSAtom name;
} JSLValue;

static int js_unsupported_keyword(JSParseState *s, JSAtom atom) {
  char buf[ATOM_GET_STR_BUF_SIZE];
  return js_parse_error(s, "unsupported keyword: %s",
                        JS_AtomGetStr(s->ctx, buf, sizeof(buf), atom));
}

static void js_emit_spread_code(JSParseState *s, int depth) {
  int label_rest_next, label_rest_done;

  /* XXX: could check if enum object is an actual array and optimize
     slice extraction. enumeration record and target array are in a
     different order from OP_append case. */
  /* enum_rec xxx -- enum_rec xxx array 0 */
  emit_op(s, OP_array_from);
  emit_u16(s, 0);
  emit_op(s, OP_push_i32);
  emit_u32(s, 0);
  emit_label(s, label_rest_next = new_label(s));
  emit_op(s, OP_for_of_next);
  emit_u8(s, 2 + depth);
  label_rest_done = emit_goto(s, OP_if_true, -1);
  /* array idx val -- array idx */
  emit_op(s, OP_define_array_el);
  emit_op(s, OP_inc);
  emit_goto(s, OP_goto, label_rest_next);
  emit_label(s, label_rest_done);
  /* enum_rec xxx array idx undef -- enum_rec xxx array */
  emit_op(s, OP_drop);
  emit_op(s, OP_drop);
}

/* Return -1 if error, 0 if no initializer, 1 if an initializer is
   present at the top level. */
int js_parse_destructing_element_GC(JSParseState *s, int tok, int is_arg,
                                    int hasval, int has_ellipsis,
                                    BOOL allow_initializer) {
  int label_parse, label_assign, label_done, label_lvalue, depth_lvalue;
  int start_addr, assign_addr;
  HandleScope func_scope(s->ctx->rt);
  JSAtom prop_name, var_name;
  int opcode, scope, tok1, skip_bits;
  BOOL has_initializer;

  if (has_ellipsis < 0) {
    /* pre-parse destructuration target for spread detection */
    js_parse_skip_parens_token(s, &skip_bits, FALSE);
    has_ellipsis = skip_bits & SKIP_HAS_ELLIPSIS;
  }

  label_parse = new_label(s);
  label_assign = new_label(s);

  start_addr = s->cur_func->byte_code.size;
  if (hasval) {
    /* consume value from the stack */
    emit_op(s, OP_dup);
    emit_op(s, OP_undefined);
    emit_op(s, OP_strict_eq);
    emit_goto(s, OP_if_true, label_parse);
    emit_label(s, label_assign);
  } else {
    emit_goto(s, OP_goto, label_parse);
    emit_label(s, label_assign);
    /* leave value on the stack */
    emit_op(s, OP_dup);
  }
  assign_addr = s->cur_func->byte_code.size;
  if (s->token.val == '{') {
    if (next_token(s)) return -1;
    /* throw an exception if the value cannot be converted to an object */
    emit_op(s, OP_to_object);
    if (has_ellipsis) {
      /* add excludeList on stack just below src object */
      emit_op(s, OP_object);
      emit_op(s, OP_swap);
    }
    while (s->token.val != '}') {
      int prop_type;
      if (s->token.val == TOK_ELLIPSIS) {
        if (!has_ellipsis) {
          LEPUS_ThrowInternalError(s->ctx, "unexpected ellipsis token");
          return -1;
        }
        if (next_token(s)) return -1;
        if (tok) {
          var_name = js_parse_destructing_var(s, tok, is_arg);
          if (var_name == JS_ATOM_NULL) return -1;
          opcode = OP_scope_get_var;
          scope = s->cur_func->scope_level;
          label_lvalue = -1;
          depth_lvalue = 0;
        } else {
          if (js_parse_left_hand_side_expr_GC(s)) return -1;

          if (get_lvalue(s, &opcode, &scope, &var_name, &label_lvalue,
                         &depth_lvalue, FALSE, '{'))
            return -1;
        }
        func_scope.PushLEPUSAtom(var_name);
        if (s->token.val != '}') {
          js_parse_error(s, "assignment rest property must be last");
          goto var_error;
        }
        emit_op(s, OP_object); /* target */
        emit_op(s, OP_copy_data_properties);
        emit_u8(s, 0 | ((depth_lvalue + 1) << 2) | ((depth_lvalue + 2) << 5));
        goto set_val;
      }
      prop_type = js_parse_property_name_GC(s, &prop_name, FALSE, TRUE, FALSE);
      func_scope.PushLEPUSAtom(prop_name);
      if (prop_type < 0) return -1;
      var_name = JS_ATOM_NULL;
      opcode = OP_scope_get_var;
      scope = s->cur_func->scope_level;
      label_lvalue = -1;
      depth_lvalue = 0;
      if (prop_type == PROP_TYPE_IDENT) {
        if (next_token(s)) goto prop_error;
        if ((s->token.val == '[' || s->token.val == '{') &&
            ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == ',' ||
             tok1 == '=' || tok1 == '}')) {
          if (prop_name == JS_ATOM_NULL) {
            /* computed property name on stack */
            if (has_ellipsis) {
              /* define the property in excludeList */
              emit_op(s, OP_to_propkey); /* avoid calling ToString twice */
              emit_op(s, OP_perm3);      /* TOS: src excludeList prop */
              emit_op(s, OP_null);       /* TOS: src excludeList prop null */
              emit_op(s, OP_define_array_el); /* TOS: src excludeList prop */
              emit_op(s, OP_perm3);           /* TOS: excludeList src prop */
            }
            /* get the computed property from the source object */
            emit_op(s, OP_get_array_el2);
          } else {
            /* named property */
            if (has_ellipsis) {
              /* define the property in excludeList */
              emit_op(s, OP_swap);         /* TOS: src excludeList */
              emit_op(s, OP_null);         /* TOS: src excludeList null */
              emit_op(s, OP_define_field); /* TOS: src excludeList */
              emit_atom(s, prop_name);
              emit_op(s, OP_swap); /* TOS: excludeList src */
            }
            /* get the named property from the source object */
            emit_op(s, OP_get_field2);
            emit_u32(s, prop_name);
          }
          if (js_parse_destructing_element_GC(s, tok, is_arg, TRUE, -1, TRUE) <
              0)
            return -1;
          if (s->token.val == '}') break;
          /* accept a trailing comma before the '}' */
          if (js_parse_expect(s, ',')) return -1;
          continue;
        }
        if (prop_name == JS_ATOM_NULL) {
          emit_op(s, OP_to_propkey2);
          if (has_ellipsis) {
            /* define the property in excludeList */
            emit_op(s, OP_perm3);
            emit_op(s, OP_null);
            emit_op(s, OP_define_array_el);
            emit_op(s, OP_perm3);
          }
          /* source prop -- source source prop */
          emit_op(s, OP_dup1);
        } else {
          if (has_ellipsis) {
            /* define the property in excludeList */
            emit_op(s, OP_swap);
            emit_op(s, OP_null);
            emit_op(s, OP_define_field);
            emit_atom(s, prop_name);
            emit_op(s, OP_swap);
          }
          /* source -- source source */
          emit_op(s, OP_dup);
        }
        if (tok) {
          var_name = js_parse_destructing_var(s, tok, is_arg);
          func_scope.PushLEPUSAtom(var_name);
          if (var_name == JS_ATOM_NULL) goto prop_error;
        } else {
          if (js_parse_left_hand_side_expr_GC(s)) goto prop_error;
        lvalue:
          if (get_lvalue(s, &opcode, &scope, &var_name, &label_lvalue,
                         &depth_lvalue, FALSE, '{'))
            goto prop_error;
          func_scope.PushLEPUSAtom(var_name);
          /* swap ref and lvalue object if any */
          if (prop_name == JS_ATOM_NULL) {
            switch (depth_lvalue) {
              case 1:
                /* source prop x -> x source prop */
                emit_op(s, OP_rot3r);
                break;
              case 2:
                /* source prop x y -> x y source prop */
                emit_op(s, OP_swap2); /* t p2 s p1 */
                break;
              case 3:
                /* source prop x y z -> x y z source prop */
                emit_op(s, OP_rot5l);
                emit_op(s, OP_rot5l);
                break;
            }
          } else {
            switch (depth_lvalue) {
              case 1:
                /* source x -> x source */
                emit_op(s, OP_swap);
                break;
              case 2:
                /* source x y -> x y source */
                emit_op(s, OP_rot3l);
                break;
              case 3:
                /* source x y z -> x y z source */
                emit_op(s, OP_rot4l);
                break;
            }
          }
        }
        if (prop_name == JS_ATOM_NULL) {
          /* computed property name on stack */
          /* XXX: should have OP_get_array_el2x with depth */
          /* source prop -- val */
          emit_op(s, OP_get_array_el);
        } else {
          /* named property */
          /* XXX: should have OP_get_field2x with depth */
          /* source -- val */
          emit_op(s, OP_get_field);
          emit_u32(s, prop_name);
        }
      } else {
        /* prop_type = PROP_TYPE_VAR, cannot be a computed property */
        if (is_arg && js_parse_check_duplicate_parameter(s, prop_name))
          goto prop_error;
        if ((s->cur_func->js_mode & JS_MODE_STRICT) &&
            (prop_name == JS_ATOM_eval || prop_name == JS_ATOM_arguments)) {
          js_parse_error(s, "invalid destructuring target");
          goto prop_error;
        }
        if (has_ellipsis) {
          /* define the property in excludeList */
          emit_op(s, OP_swap);
          emit_op(s, OP_null);
          emit_op(s, OP_define_field);
          emit_atom(s, prop_name);
          emit_op(s, OP_swap);
        }
        if (!tok || tok == TOK_VAR) {
          /* generate reference */
          /* source -- source source */
          emit_op(s, OP_dup);
          emit_op(s, OP_scope_get_var);
          emit_atom(s, prop_name);
          emit_u16(s, s->cur_func->scope_level);
          goto lvalue;
        }
        var_name = prop_name;
        func_scope.PushLEPUSAtom(var_name);
        /* source -- source val */
        emit_op(s, OP_get_field2);
        emit_u32(s, prop_name);
      }
    set_val:
      if (tok) {
        if (js_define_var(s, var_name, tok)) goto var_error;
        scope = s->cur_func->scope_level;
      }
      if (s->token.val == '=') { /* handle optional default value */
        int label_hasval;
        emit_op(s, OP_dup);
        emit_op(s, OP_undefined);
        emit_op(s, OP_strict_eq);
        label_hasval = emit_goto(s, OP_if_false, -1);
        if (next_token(s)) goto var_error;
        emit_op(s, OP_drop);
        if (js_parse_assign_expr(s, PF_IN_ACCEPTED)) goto var_error;
        if (opcode == OP_scope_get_var || opcode == OP_get_ref_value)
          set_object_name(s, var_name);
        emit_label(s, label_hasval);
      }
      /* store value into lvalue object */
      put_lvalue(s, opcode, scope, var_name, label_lvalue,
                 PUT_LVALUE_NOKEEP_DEPTH, (tok == TOK_CONST || tok == TOK_LET));
      if (s->token.val == '}') break;
      /* accept a trailing comma before the '}' */
      if (js_parse_expect(s, ',')) return -1;
    }
    /* drop the source object */
    emit_op(s, OP_drop);
    if (has_ellipsis) {
      emit_op(s, OP_drop); /* pop excludeList */
    }
    if (next_token(s)) return -1;
  } else if (s->token.val == '[') {
    BOOL has_spread;
    int enum_depth;
    BlockEnv block_env;

    if (next_token(s)) return -1;
    /* the block environment is only needed in generators in case
       'yield' triggers a 'return' */
    push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL, -1, -1, 2);
    block_env.has_iterator = TRUE;
    emit_op(s, OP_for_of_start);
    has_spread = FALSE;
    while (s->token.val != ']') {
      /* get the next value */
      if (s->token.val == TOK_ELLIPSIS) {
        if (next_token(s)) return -1;
        if (s->token.val == ',' || s->token.val == ']')
          return js_parse_error(s, "missing binding pattern...");
        has_spread = TRUE;
      }
      if (s->token.val == ',') {
        /* do nothing, skip the value, has_spread is false */
        emit_op(s, OP_for_of_next);
        emit_u8(s, 0);
        emit_op(s, OP_drop);
        emit_op(s, OP_drop);
      } else if ((s->token.val == '[' || s->token.val == '{') &&
                 ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) ==
                      ',' ||
                  tok1 == '=' || tok1 == ']')) {
        if (has_spread) {
          if (tok1 == '=')
            return js_parse_error(s,
                                  "rest element cannot have a default value");
          js_emit_spread_code(s, 0);
        } else {
          emit_op(s, OP_for_of_next);
          emit_u8(s, 0);
          emit_op(s, OP_drop);
        }
        if (js_parse_destructing_element_GC(
                s, tok, is_arg, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0)
          return -1;
      } else {
        var_name = JS_ATOM_NULL;
        enum_depth = 0;
        if (tok) {
          var_name = js_parse_destructing_var(s, tok, is_arg);
          if (var_name == JS_ATOM_NULL) goto var_error;
          func_scope.PushLEPUSAtom(var_name);
          if (js_define_var(s, var_name, tok)) goto var_error;
          opcode = OP_scope_get_var;
          scope = s->cur_func->scope_level;
        } else {
          if (js_parse_left_hand_side_expr_GC(s)) return -1;
          if (get_lvalue(s, &opcode, &scope, &var_name, &label_lvalue,
                         &enum_depth, FALSE, '[')) {
            return -1;
          }
          func_scope.PushLEPUSAtom(var_name);
        }
        if (has_spread) {
          js_emit_spread_code(s, enum_depth);
        } else {
          emit_op(s, OP_for_of_next);
          emit_u8(s, enum_depth);
          emit_op(s, OP_drop);
        }
        if (s->token.val == '=' && !has_spread) {
          /* handle optional default value */
          int label_hasval;
          emit_op(s, OP_dup);
          emit_op(s, OP_undefined);
          emit_op(s, OP_strict_eq);
          label_hasval = emit_goto(s, OP_if_false, -1);
          if (next_token(s)) goto var_error;
          emit_op(s, OP_drop);
          if (js_parse_assign_expr(s, PF_IN_ACCEPTED)) goto var_error;
          if (opcode == OP_scope_get_var || opcode == OP_get_ref_value)
            set_object_name(s, var_name);
          emit_label(s, label_hasval);
        }
        /* store value into lvalue object */
        put_lvalue(s, opcode, scope, var_name, label_lvalue,
                   PUT_LVALUE_NOKEEP_DEPTH,
                   (tok == TOK_CONST || tok == TOK_LET));
      }
      if (s->token.val == ']') break;
      if (has_spread)
        return js_parse_error(s, "rest element must be the last one");
      /* accept a trailing comma before the ']' */
      if (js_parse_expect(s, ',')) return -1;
    }
    /* close iterator object:
       if completed, enum_obj has been replaced by undefined */
    emit_op(s, OP_iterator_close);
    pop_break_entry(s->cur_func);
    if (next_token(s)) return -1;
  } else {
    return js_parse_error(s, "invalid assignment syntax");
  }
  if (s->token.val == '=' && allow_initializer) {
    label_done = emit_goto(s, OP_goto, -1);
    if (next_token(s)) return -1;
    emit_label(s, label_parse);
    if (hasval) emit_op(s, OP_drop);
    if (js_parse_assign_expr(s, PF_IN_ACCEPTED)) return -1;
    emit_goto(s, OP_goto, label_assign);
    emit_label(s, label_done);
    has_initializer = TRUE;
  } else {
    /* normally hasval is true except if
      js_parse_skip_parens_token() was wrong in the parsing */
    //        assert(hasval);
    if (!hasval) {
      js_parse_error(s, "too complicated destructuring expression");
      return -1;
    }
    /* remove test and decrement label ref count */
    memset(s->cur_func->byte_code.buf + start_addr, OP_nop,
           assign_addr - start_addr);
    s->cur_func->label_slots[label_parse].ref_count--;
    has_initializer = FALSE;
  }
  return has_initializer;

prop_error:
var_error:
  return -1;
}

typedef enum FuncCallType {
  FUNC_CALL_NORMAL,
  FUNC_CALL_NEW,
  FUNC_CALL_SUPER_CTOR,
  FUNC_CALL_TEMPLATE,
} FuncCallType;

/* allowed parse_flags: PF_POSTFIX_CALL, PF_ARROW_FUNC */
static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) {
  FuncCallType call_type;
  int optional_chaining_label;
  BOOL accept_lparen = (parse_flags & PF_POSTFIX_CALL) != 0;

  call_type = FUNC_CALL_NORMAL;

  const uint8_t *caller_start = nullptr, *caller_end = nullptr;
  caller_start = s->token.ptr;

  int is_parsing_newnew_pattern = 0;
  switch (s->token.val) {
    case TOK_NUMBER: {
      LEPUSValue val;
      val = s->token.u.num.val;

      if (LEPUS_VALUE_IS_INT(val)) {
        emit_op(s, OP_push_i32);
        emit_u32(s, LEPUS_VALUE_GET_INT(val));
      } else {
        if (emit_push_const(s, val, 0) < 0) return -1;
      }
    }
      if (next_token(s)) return -1;
      break;
    case TOK_TEMPLATE:
      if (js_parse_template(s, 0, NULL)) return -1;
      break;
    case TOK_STRING:
      if (emit_push_const(s, s->token.u.str.str, 1)) return -1;
      if (next_token(s)) return -1;
      break;

    case TOK_DIV_ASSIGN:
      s->buf_ptr -= 2;
      goto parse_regexp;
    case '/':
      s->buf_ptr--;
    parse_regexp: {
      LEPUSValue str;
      int ret;
      if (!s->ctx->compile_regexp)
        return js_parse_error(s, "RegExp are not supported");
      /* the previous token is '/' or '/=', so no need to free */
      if (js_parse_regexp(s)) return -1;
      ret = emit_push_const(s, s->token.u.regexp.body, 0);
      str = s->ctx->compile_regexp(s->ctx, s->token.u.regexp.body,
                                   s->token.u.regexp.flags);
      if (LEPUS_IsException(str)) {
        /* add the line number info */
        build_backtrace(s->ctx, s->ctx->rt->current_exception, s->filename,
                        s->token.line_num, NULL, 0);
        return -1;
      }
      HandleScope block_scope2(s->ctx, &str, HANDLE_TYPE_LEPUS_VALUE);
      ret = emit_push_const(s, str, 0);
      if (ret) return -1;
      /* we use a specific opcode to be sure the correct
         function is called (otherwise the bytecode would have
         to be verified by the RegExp constructor) */
      emit_op(s, OP_regexp);
      if (next_token(s)) return -1;
    } break;
    case '(':
      if ((parse_flags & PF_ARROW_FUNC) &&
          js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) {
        if (js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, JS_FUNC_NORMAL,
                                   JS_ATOM_NULL, s->token.ptr,
                                   s->token.line_num))
          return -1;
      } else {
        if (js_parse_expr_paren(s)) return -1;
      }
      break;
    case TOK_FUNCTION:
      if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR, JS_FUNC_NORMAL,
                                 JS_ATOM_NULL, s->token.ptr, s->token.line_num))
        return -1;
      break;
    case TOK_CLASS:
      if (js_parse_class(s, TRUE, JS_PARSE_EXPORT_NONE)) return -1;
      break;
    case TOK_NULL:
      if (next_token(s)) return -1;
      emit_op(s, OP_null);
      break;
    case TOK_THIS:
      if (next_token(s)) return -1;
      emit_op(s, OP_scope_get_var);
      emit_atom(s, JS_ATOM_this);
      emit_u16(s, 0);
      break;
    case TOK_FALSE:
      if (next_token(s)) return -1;
      emit_op(s, OP_push_false);
      break;
    case TOK_TRUE:
      if (next_token(s)) return -1;
      emit_op(s, OP_push_true);
      break;
    case TOK_IDENT: {
      JSAtom name;
      if (s->token.u.ident.is_reserved) {
        return js_parse_error_reserved_identifier(s);
      }
      if ((parse_flags & PF_ARROW_FUNC) && peek_token(s, TRUE) == TOK_ARROW) {
        if (js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, JS_FUNC_NORMAL,
                                   JS_ATOM_NULL, s->token.ptr,
                                   s->token.line_num))
          return -1;
      } else if (token_is_pseudo_keyword(s, JS_ATOM_async) &&
                 peek_token(s, TRUE) != '\n') {
        const uint8_t *source_ptr;
        int source_line_num;

        source_ptr = s->token.ptr;
        source_line_num = s->token.line_num;
        if (next_token(s)) return -1;
        if (s->token.val == TOK_FUNCTION) {
          if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR, JS_FUNC_ASYNC,
                                     JS_ATOM_NULL, source_ptr, source_line_num))
            return -1;
        } else if ((parse_flags & PF_ARROW_FUNC) &&
                   ((s->token.val == '(' &&
                     js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) ||
                    (s->token.val == TOK_IDENT &&
                     !s->token.u.ident.is_reserved &&
                     peek_token(s, TRUE) == TOK_ARROW))) {
          if (js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, JS_FUNC_ASYNC,
                                     JS_ATOM_NULL, source_ptr, source_line_num))
            return -1;
        } else {
          name = JS_ATOM_async;
          goto do_get_var;
        }
      } else {
        if (s->token.u.ident.atom == JS_ATOM_arguments &&
            !s->cur_func->arguments_allowed) {
          js_parse_error(s,
                         "'arguments' identifier is not allowed in class field "
                         "initializer");
          return -1;
        }
        name = s->token.u.ident.atom;
        if (next_token(s)) /* update line number before emitting code */
          return -1;
      do_get_var:
        emit_op(s, OP_scope_get_var);
        emit_u32(s, name);
        emit_u16(s, s->cur_func->scope_level);
      }
    } break;
    case '{':
    case '[': {
      int skip_bits;
      if (js_parse_skip_parens_token(s, &skip_bits, FALSE) == '=') {
        if (js_parse_destructing_element_GC(
                s, 0, 0, FALSE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0)
          return -1;
      } else {
        if (s->token.val == '{') {
          if (js_parse_object_literal_GC(s)) return -1;
        } else {
          if (js_parse_array_literal(s)) return -1;
        }
      }
    } break;
    case TOK_NEW:
      if (next_token(s)) return -1;
      if (s->token.val == '.') {
        if (next_token(s)) return -1;
        if (!token_is_pseudo_keyword(s, JS_ATOM_target))
          return js_parse_error(s, "expecting target");
        if (!s->cur_func->new_target_allowed)
          return js_parse_error(s, "new.target only allowed within functions");
        if (next_token(s)) return -1;
        emit_op(s, OP_scope_get_var);
        emit_atom(s, JS_ATOM_new_target);
        emit_u16(s, 0);
      } else {
        caller_start = s->token.ptr;
        if (js_parse_postfix_expr(s, FALSE | PF_LASTEST_ISNEW)) return -1;
        caller_end = s->token.ptr;
        is_parsing_newnew_pattern = parse_flags & PF_LASTEST_ISNEW;
        accept_lparen = TRUE;
        if (s->token.val != '(') {
          /* new operator on an object */
          if (!emit_name_str(s, caller_start, caller_end)) return -1;
          emit_op(s, OP_dup);
          emit_op(s, OP_call_constructor);
          emit_u16(s, 0);
        } else {
          call_type = FUNC_CALL_NEW;
        }
      }
      break;
    case TOK_SUPER:
      if (next_token(s)) return -1;
      if (s->token.val == '(') {
        if (!s->cur_func->super_call_allowed)
          return js_parse_error(
              s, "super() is only valid in a derived class constructor");
        call_type = FUNC_CALL_SUPER_CTOR;
      } else if (s->token.val == '.' || s->token.val == '[') {
        if (!s->cur_func->super_allowed)
          return js_parse_error(s, "'super' is only valid in a method");
        emit_op(s, OP_scope_get_var);
        emit_atom(s, JS_ATOM_this);
        emit_u16(s, 0);
        emit_op(s, OP_scope_get_var);
        emit_atom(s, JS_ATOM_home_object);
        emit_u16(s, 0);
        emit_op(s, OP_get_super);
      } else {
        return js_parse_error(s, "invalid use of 'super'");
      }
      break;
    case TOK_IMPORT:
      if (!accept_lparen) return js_parse_error(s, "invalid use of 'import'");
      if (next_token(s)) return -1;
      if (js_parse_expect(s, '(')) return -1;
      if (js_parse_assign_expr(s, PF_IN_ACCEPTED)) return -1;
      if (js_parse_expect(s, ')')) return -1;
      emit_op(s, OP_import);
      break;
    default:
      return js_parse_error(s, "unexpected token in expression: '%.*s'",
                            static_cast<int>(s->buf_ptr - s->token.ptr),
                            s->token.ptr);
  }

  optional_chaining_label = -1;
  for (;;) {
    JSFunctionDef *fd = s->cur_func;
    BOOL has_optional_chain = FALSE;

    if (s->token.val == TOK_QUESTION_MARK_DOT) {
      /* optional chaining */
      if (next_token(s)) return -1;
      has_optional_chain = TRUE;
      if (s->token.val == '(' && accept_lparen) {
        goto parse_func_call;
      } else if (s->token.val == '[') {
        goto parse_array_access;
      } else {
        goto parse_property;
      }
    } else if (s->token.val == TOK_TEMPLATE && call_type == FUNC_CALL_NORMAL) {
      if (optional_chaining_label >= 0) {
        return js_parse_error(
            s, "template literal cannot appear in an optional chain");
      }
      call_type = FUNC_CALL_TEMPLATE;
      caller_end = s->token.ptr;
      goto parse_func_call2;
    }
    if (s->token.val == '(' && accept_lparen) {
      if (is_parsing_newnew_pattern) accept_lparen = FALSE;
      int opcode;
      int arg_count;
      int drop_count;
      // <ByeDance begin>
      s->func_call_ptr = s->token.ptr;
      if (s->func_call_ptr < s->utf8_parse_front) {
        s->func_call_adapte_size =
            s->utf8_adapte_size - s->last_utf8_adapte_size;
      } else {
        s->func_call_adapte_size = s->utf8_adapte_size;
      }
      // <Primjs end>

      /* function call */
    parse_func_call:
      caller_end = s->token.ptr;
      if (next_token(s)) return -1;

      if (call_type == FUNC_CALL_NORMAL) {
      parse_func_call2:
        switch (opcode = get_prev_opcode(fd)) {
          case OP_get_field:
            /* keep the object on the stack */
            fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2;
            drop_count = 2;
            break;
          case OP_scope_get_private_field:
            /* keep the object on the stack */
            fd->byte_code.buf[fd->last_opcode_pos] =
                OP_scope_get_private_field2;
            drop_count = 2;
            break;
          case OP_get_array_el:
            /* keep the object on the stack */
            fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2;
            drop_count = 2;
            break;
          case OP_scope_get_var: {
            JSAtom name;
            int scope;
            name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
            scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5);
            if (name == JS_ATOM_eval && call_type == FUNC_CALL_NORMAL &&
                !has_optional_chain) {
              /* direct 'eval' */
              fd->byte_code.size = fd->last_opcode_pos;
              fd->last_opcode_pos = -1;
              opcode = OP_eval;
            } else {
              /* verify if function name resolves to a simple
                 get_loc/get_arg: a function call inside a `with`
                 statement can resolve to a method call of the
                 `with` context object
               */
              /* XXX: always generate the OP_scope_get_ref
                 and remove it in variable resolution
                 pass ? */
              if (has_with_scope(fd, scope)) {
                opcode = OP_scope_get_ref;
                fd->byte_code.buf[fd->last_opcode_pos] = opcode;
              }
            }
            drop_count = 1;
          } break;
          case OP_get_super_value:
            fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el;
            /* on stack: this func_obj */
            opcode = OP_get_array_el;
            drop_count = 2;
            break;
          default:
            opcode = OP_invalid;
            drop_count = 1;
            break;
        }
        if (has_optional_chain) {
          optional_chain_test(s, &optional_chaining_label, drop_count);
        }
      } else {
        opcode = OP_invalid;
      }

      if (call_type == FUNC_CALL_TEMPLATE) {
        if (js_parse_template(s, 1, &arg_count)) return -1;
        goto emit_func_call;
      } else if (call_type == FUNC_CALL_SUPER_CTOR) {
        emit_op(s, OP_scope_get_var);
        emit_atom(s, JS_ATOM_this_active_func);
        emit_u16(s, 0);

        emit_op(s, OP_get_super_ctor);

        emit_op(s, OP_scope_get_var);
        emit_atom(s, JS_ATOM_new_target);
        emit_u16(s, 0);
      } else if (call_type == FUNC_CALL_NEW) {
        emit_op(s, OP_dup); /* new.target = function */
      }

      /* parse arguments */
      arg_count = 0;
      while (s->token.val != ')') {
        if (arg_count >= 65535) {
          return js_parse_error(s, "Too many call arguments");
        }
        if (s->token.val == TOK_ELLIPSIS) break;
        if (js_parse_assign_expr(s, PF_IN_ACCEPTED)) return -1;
        arg_count++;
        if (s->token.val == ')') break;
        /* accept a trailing comma before the ')' */
        if (js_parse_expect(s, ',')) return -1;
      }
      if (s->token.val == TOK_ELLIPSIS) {
        emit_op(s, OP_array_from);
        emit_u16(s, arg_count);
        emit_op(s, OP_push_i32);
        emit_u32(s, arg_count);

        /* on stack: array idx */
        while (s->token.val != ')') {
          if (s->token.val == TOK_ELLIPSIS) {
            if (next_token(s)) return -1;
            if (js_parse_assign_expr(s, PF_IN_ACCEPTED)) return -1;
#if 1
            /* XXX: could pass is_last indicator? */
            emit_op(s, OP_append);
#else
            int label_next, label_done;
            label_next = new_label(s);
            label_done = new_label(s);
            /* push enumerate object below array/idx pair */
            emit_op(s, OP_for_of_start);
            emit_op(s, OP_rot5l);
            emit_op(s, OP_rot5l);
            emit_label(s, label_next);
            /* on stack: enum_rec array idx */
            emit_op(s, OP_for_of_next);
            emit_u8(s, 2);
            emit_goto(s, OP_if_true, label_done);
            /* append element */
            /* enum_rec array idx val -> enum_rec array new_idx */
            emit_op(s, OP_define_array_el);
            emit_op(s, OP_inc);
            emit_goto(s, OP_goto, label_next);
            emit_label(s, label_done);
            /* close enumeration, drop enum_rec and idx */
            emit_op(s, OP_drop); /* drop undef */
            emit_op(s, OP_nip1); /* drop enum_rec */
            emit_op(s, OP_nip1);
            emit_op(s, OP_nip1);
#endif
          } else {
            if (js_parse_assign_expr(s, PF_IN_ACCEPTED)) return -1;
            /* array idx val */
            emit_op(s, OP_define_array_el);
            emit_op(s, OP_inc);
          }
          if (s->token.val == ')') break;
          /* accept a trailing comma before the ')' */
          if (js_parse_expect(s, ',')) return -1;
        }
        if (next_token(s)) return -1;
        /* drop the index */
        emit_op(s, OP_drop);

        /* apply function call */
        switch (opcode) {
          case OP_get_field:
          case OP_scope_get_private_field:
          case OP_get_array_el:
          case OP_scope_get_ref:
            /* obj func array -> func obj array */
            emit_op(s, OP_perm3);
            emit_op(s, OP_apply);
            emit_u16(s, call_type == FUNC_CALL_NEW);
            break;
          case OP_eval:
            /* eval needs a single argument */
            emit_op(s, OP_get_field);
            emit_atom(s, __JS_AtomFromUInt32(0));
            emit_op(s, OP_eval);
            emit_u16(s, fd->scope_level);
            fd->has_eval_call = TRUE;
            break;
          default:
            if (call_type == FUNC_CALL_SUPER_CTOR) {
              emit_op(s, OP_apply);
              emit_u16(s, 1);
              /* set the 'this' value */
              emit_op(s, OP_dup);
              emit_op(s, OP_scope_put_var_init);
              emit_atom(s, JS_ATOM_this);
              emit_u16(s, 0);

              emit_class_field_init(s);
            } else if (call_type == FUNC_CALL_NEW) {
              /* obj func array -> func obj array */
              emit_op(s, OP_perm3);
              emit_op(s, OP_apply);
              emit_u16(s, 1);
            } else {
              /* func array -> func undef array */
              emit_op(s, OP_undefined);
              emit_op(s, OP_swap);
              emit_op(s, OP_apply);
              emit_u16(s, 0);
            }
            break;
        }
      } else {
        if (next_token(s)) return -1;
      emit_func_call:
        switch (opcode) {
          case OP_get_field:
          case OP_scope_get_private_field:
          case OP_get_array_el:
          case OP_scope_get_ref:
            if (!emit_name_str(s, caller_start, caller_end)) return -1;
            emit_op(s, OP_call_method);
            emit_u16(s, arg_count);
            break;
          case OP_eval:
            /* eval needs a single argument */
            if (arg_count == 0) {
              /* no actual eval call is needed? */
              emit_op(s, OP_undefined);
            } else {
              int i;
              /* pop extra arguments. Contrary to ECMA spec,
                 browsers seem to evaluate extra arguments
                 to direct eval */
              for (i = 1; i < arg_count; i++) emit_op(s, OP_drop);
              emit_op(s, OP_eval);
              emit_u16(s, fd->scope_level);
              fd->has_eval_call = TRUE;
            }
            break;
          default:
            if (!emit_name_str(s, caller_start, caller_end)) return -1;
            if (call_type == FUNC_CALL_SUPER_CTOR) {
              emit_op(s, OP_call_constructor);
              emit_u16(s, arg_count);

              /* set the 'this' value */
              emit_op(s, OP_dup);
              emit_op(s, OP_scope_put_var_init);
              emit_atom(s, JS_ATOM_this);
              emit_u16(s, 0);

              emit_class_field_init(s);
            } else if (call_type == FUNC_CALL_NEW) {
              emit_op(s, OP_call_constructor);
              emit_u16(s, arg_count);
            } else {
              emit_op(s, OP_call);
              emit_u16(s, arg_count);
            }
            break;
        }
      }
      // <ByeDance begin>
      s->func_call_ptr = NULL;
      s->func_call_adapte_size = -1;
      // <Primjs end>
      call_type = FUNC_CALL_NORMAL;
    } else if (s->token.val == '.') {
      if (next_token(s)) return -1;
    parse_property:
      if (s->token.val == TOK_PRIVATE_NAME) {
        /* private class field */
        if (get_prev_opcode(fd) == OP_get_super) {
          return js_parse_error(s, "private class field forbidden after super");
        }
        if (has_optional_chain) {
          optional_chain_test(s, &optional_chaining_label, 1);
        }
        emit_op(s, OP_scope_get_private_field);
        emit_atom(s, s->token.u.ident.atom);
        emit_u16(s, s->cur_func->scope_level);
      } else {
        if (!token_is_ident(s->token.val)) {
          return js_parse_error(s, "expecting field name");
        }
        if (get_prev_opcode(fd) == OP_get_super) {
          LEPUSValue val;
          int ret;
          val = JS_AtomToValue_GC(s->ctx, s->token.u.ident.atom);
          HandleScope block_scope(s->ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
          ret = emit_push_const(s, val, 1);
          if (ret) return -1;
          emit_op(s, OP_get_super_value);
        } else {
          if (has_optional_chain) {
            optional_chain_test(s, &optional_chaining_label, 1);
          }
          emit_op(s, OP_get_field);
          emit_atom(s, s->token.u.ident.atom);
        }
      }
      if (next_token(s)) return -1;
    } else if (s->token.val == '[') {
      int prev_op;
    parse_array_access:
      prev_op = get_prev_opcode(fd);
      if (has_optional_chain) {
        optional_chain_test(s, &optional_chaining_label, 1);
      }
      if (next_token(s)) return -1;
      if (js_parse_expr(s)) return -1;
      if (js_parse_expect(s, ']')) return -1;
      if (prev_op == OP_get_super) {
        emit_op(s, OP_get_super_value);
      } else {
        emit_op(s, OP_get_array_el);
      }
    } else {
      break;
    }
  }
  if (optional_chaining_label >= 0) emit_label(s, optional_chaining_label);
  return 0;
}

static __exception int js_parse_delete(JSParseState *s) {
  JSFunctionDef *fd = s->cur_func;
  JSAtom name;
  int opcode;

  if (next_token(s)) return -1;
  if (js_parse_unary_GC(s, -1)) return -1;
  switch (opcode = get_prev_opcode(fd)) {
    case OP_get_field: {
      HandleScope block_scope(s->ctx);
      LEPUSValue val;
      int ret;

      name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
      block_scope.PushLEPUSAtom(name);
      fd->byte_code.size = fd->last_opcode_pos;
      fd->last_opcode_pos = -1;
      val = JS_AtomToValue_GC(s->ctx, name);
      block_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
      ret = emit_push_const(s, val, 1);
      if (ret) return ret;
    }
      goto do_delete;
    case OP_get_array_el:
      fd->byte_code.size = fd->last_opcode_pos;
      fd->last_opcode_pos = -1;
    do_delete:
      emit_op(s, OP_delete);
      break;
    case OP_scope_get_var:
      /* 'delete this': this is not a reference */
      name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
      if (name == JS_ATOM_this || name == JS_ATOM_new_target) goto ret_true;
      if (fd->js_mode & JS_MODE_STRICT) {
        return js_parse_error(
            s, "cannot delete a direct reference in strict mode");
      } else {
        fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_delete_var;
      }
      break;
    case OP_scope_get_private_field:
      return js_parse_error(s, "cannot delete a private class field");
    case OP_get_super_value:
      emit_op(s, OP_throw_var);
      emit_atom(s, JS_ATOM_NULL);
      emit_u8(s, JS_THROW_ERROR_DELETE_SUPER);
      break;
    default:
    ret_true:
      emit_op(s, OP_drop);
      emit_op(s, OP_push_true);
      break;
  }
  return 0;
}

/* allowed parse_flags: PF_ARROW_FUNC, PF_POW_ALLOWED, PF_POW_FORBIDDEN */
__exception int js_parse_unary_GC(JSParseState *s, int parse_flags) {
  int op;

  switch (s->token.val) {
    case '+':
    case '-':
    case '!':
    case '~':
    case TOK_VOID:
      op = s->token.val;
      if (next_token(s)) return -1;
      if (js_parse_unary_GC(s, PF_POW_FORBIDDEN)) return -1;
      switch (op) {
        case '-':
          emit_op(s, OP_neg);
          break;
        case '+':
          emit_op(s, OP_plus);
          break;
        case '!':
          emit_op(s, OP_lnot);
          break;
        case '~':
          emit_op(s, OP_not);
          break;
        case TOK_VOID:
          emit_op(s, OP_drop);
          emit_op(s, OP_undefined);
          break;
        default:
          abort();
      }
      parse_flags = 0;
      break;
    case TOK_DEC:
    case TOK_INC: {
      int opcode, op, scope, label;
      JSAtom name;
      op = s->token.val;
      if (next_token(s)) return -1;
      /* XXX: should parse LeftHandSideExpression */
      if (js_parse_unary_GC(s, 0)) return -1;
      if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op))
        return -1;
      HandleScope func_scope(s->ctx->rt);
      func_scope.PushLEPUSAtom(name);
      emit_op(s, OP_dec + op - TOK_DEC);
      put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE);
    } break;
    case TOK_TYPEOF: {
      JSFunctionDef *fd;
      if (next_token(s)) return -1;
      if (js_parse_unary_GC(s, -1)) return -1;
      /* reference access should not return an exception, so we
         patch the get_var */
      fd = s->cur_func;
      if (get_prev_opcode(fd) == OP_scope_get_var) {
        fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_get_var_undef;
      }
      emit_op(s, OP_typeof);
      parse_flags = 0;
    } break;
    case TOK_DELETE:
      if (js_parse_delete(s)) return -1;
      parse_flags = 0;
      break;
    case TOK_AWAIT:
      if (!(s->cur_func->func_kind & JS_FUNC_ASYNC))
        return js_parse_error(s, "unexpected 'await' keyword");
      if (!s->cur_func->in_function_body)
        return js_parse_error(s, "await in default expression");
      if (next_token(s)) return -1;
      if (js_parse_unary_GC(s, -1)) return -1;
      emit_op(s, OP_await);
      parse_flags = 0;
      break;
    default:
      if (js_parse_postfix_expr(
              s, (parse_flags & PF_ARROW_FUNC) | PF_POSTFIX_CALL))
        return -1;
      if (!s->got_lf && (s->token.val == TOK_DEC || s->token.val == TOK_INC)) {
        int opcode, op, scope, label;
        JSAtom name;
        op = s->token.val;
        if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op))
          return -1;
        HandleScope func_scope(s->ctx->rt);
        func_scope.PushLEPUSAtom(name);
        emit_op(s, OP_post_dec + op - TOK_DEC);
        put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_SECOND,
                   FALSE);
        if (next_token(s)) return -1;
      }
      break;
  }
  if (parse_flags & (PF_POW_ALLOWED | PF_POW_FORBIDDEN)) {
    if (s->token.val == TOK_POW) {
      /* Strict ES7 exponentiation syntax rules: To solve
         conficting semantics between different implementations
         regarding the precedence of prefix operators and the
         postifx exponential, ES7 specifies that -2**2 is a
         syntax error. */
      if (parse_flags & PF_POW_FORBIDDEN) {
        LEPUS_ThrowSyntaxError(
            s->ctx,
            "unparenthesized unary expression can't appear on "
            "the left-hand side of '**'");
        return -1;
      }
      if (next_token(s)) return -1;
      if (js_parse_unary_GC(s, PF_POW_ALLOWED)) return -1;
      emit_op(s, OP_pow);
    }
  }
  return 0;
}

static void push_break_entry(JSFunctionDef *fd, BlockEnv *be, JSAtom label_name,
                             int label_break, int label_cont, int drop_count) {
  be->prev = fd->top_break;
  fd->top_break = be;
  be->label_name = label_name;
  be->label_break = label_break;
  be->label_cont = label_cont;
  be->drop_count = drop_count;
  be->label_finally = -1;
  be->scope_level = fd->scope_level;
  be->has_iterator = FALSE;
}

static void pop_break_entry(JSFunctionDef *fd) {
  BlockEnv *be;
  be = fd->top_break;
  fd->top_break = be->prev;
}

#define DECL_MASK_FUNC (1 << 0) /* allow normal function declaration */
/* ored with DECL_MASK_FUNC if function declarations are allowed with a label */
#define DECL_MASK_FUNC_WITH_LABEL (1 << 1)
#define DECL_MASK_OTHER (1 << 2) /* all other declarations */
#define DECL_MASK_ALL \
  (DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL | DECL_MASK_OTHER)

static __exception int js_parse_statement_or_decl(JSParseState *s,
                                                  int decl_mask);

static __exception int js_parse_statement(JSParseState *s) {
  return js_parse_statement_or_decl(s, 0);
}

static __exception int js_parse_block(JSParseState *s) {
  if (js_parse_expect(s, '{')) return -1;
  if (s->token.val != '}') {
    push_scope(s);
    for (;;) {
      if (js_parse_statement_or_decl(s, DECL_MASK_ALL)) return -1;
      if (s->token.val == '}') break;
    }
    pop_scope(s);
  }
  if (next_token(s)) return -1;
  return 0;
}

/* test if the current token is a label. Use simplistic look-ahead scanner */
static BOOL is_label(JSParseState *s) {
  return (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved &&
          peek_token(s, FALSE) == ':');
}

/* XXX: handle IteratorClose when exiting the loop before the
   enumeration is done */
static __exception int js_parse_for_in_of(JSParseState *s, int label_name,
                                          BOOL is_async) {
  LEPUSContext *ctx = s->ctx;
  JSFunctionDef *fd = s->cur_func;
  JSAtom var_name;
  HandleScope func_scope(ctx->rt);
  BOOL has_initializer, is_for_of, has_destructuring;
  int tok, tok1, opcode, scope, block_scope_level;
  int label_next, label_expr, label_cont, label_body, label_break;
  int pos_next, pos_expr;
  BlockEnv break_entry;

  has_initializer = FALSE;
  has_destructuring = FALSE;
  is_for_of = FALSE;
  block_scope_level = fd->scope_level;
  label_cont = new_label(s);
  label_body = new_label(s);
  label_break = new_label(s);
  label_next = new_label(s);

  /* create scope for the lexical variables declared in the enumeration
     expressions. XXX: Not completely correct because of weird capturing
     semantics in `for (i of o) a.push(function(){return i})` */
  push_scope(s);

  /* local for_in scope starts here so individual elements
     can be closed in statement. */
  push_break_entry(s->cur_func, &break_entry, label_name, label_break,
                   label_cont, 1);
  break_entry.scope_level = block_scope_level;

  label_expr = emit_goto(s, OP_goto, -1);

  pos_next = s->cur_func->byte_code.size;
  emit_label(s, label_next);

  tok = s->token.val;
  switch (is_let(s, DECL_MASK_OTHER)) {
    case TRUE:
      tok = TOK_LET;
      break;
    case FALSE:
      break;
    default:
      return -1;
  }
  if (tok == TOK_VAR || tok == TOK_LET || tok == TOK_CONST) {
    if (next_token(s)) return -1;

    if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)) {
      if (s->token.val == '[' || s->token.val == '{') {
        if (js_parse_destructing_element_GC(s, tok, 0, TRUE, -1, FALSE) < 0)
          return -1;
        has_destructuring = TRUE;
      } else {
        return js_parse_error(s, "variable name expected");
      }
      var_name = JS_ATOM_NULL;
    } else {
      var_name = s->token.u.ident.atom;
      if (next_token(s)) {
        return -1;
      }
      if (js_define_var(s, var_name, tok)) {
        return -1;
      }
      emit_op(s, (tok == TOK_CONST || tok == TOK_LET) ? OP_scope_put_var_init
                                                      : OP_scope_put_var);
      emit_atom(s, var_name);
      emit_u16(s, fd->scope_level);
    }
  } else {
    int skip_bits;
    if ((s->token.val == '[' || s->token.val == '{') &&
        ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == TOK_IN ||
         tok1 == TOK_OF)) {
      if (js_parse_destructing_element_GC(
              s, 0, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0)
        return -1;
    } else {
      int lvalue_label, depth;
      if (js_parse_left_hand_side_expr_GC(s)) return -1;
      if (get_lvalue(s, &opcode, &scope, &var_name, &lvalue_label, &depth,
                     FALSE, TOK_FOR))
        return -1;
      func_scope.PushLEPUSAtom(var_name);
      put_lvalue(s, opcode, scope, var_name, lvalue_label,
                 PUT_LVALUE_NOKEEP_BOTTOM, FALSE);
    }
    var_name = JS_ATOM_NULL;
  }
  emit_goto(s, OP_goto, label_body);

  pos_expr = s->cur_func->byte_code.size;
  emit_label(s, label_expr);
  if (s->token.val == '=') {
    /* XXX: potential scoping issue if inside `with` statement */
    has_initializer = TRUE;
    /* parse and evaluate initializer prior to evaluating the
       object (only used with "for in" with a non lexical variable
       in non strict mode */
    if (next_token(s) || js_parse_assign_expr(s, 0)) {
      return -1;
    }
    if (var_name != JS_ATOM_NULL) {
      emit_op(s, OP_scope_put_var);
      emit_atom(s, var_name);
      emit_u16(s, fd->scope_level);
    }
  }

  if (token_is_pseudo_keyword(s, JS_ATOM_of)) {
    break_entry.has_iterator = is_for_of = TRUE;
    break_entry.drop_count += 2;
    if (has_initializer) goto initializer_error;
  } else if (s->token.val == TOK_IN) {
    if (is_async)
      return js_parse_error(s, "'for await' loop should be used with 'of'");
    if (has_initializer && (tok != TOK_VAR || (fd->js_mode & JS_MODE_STRICT) ||
                            has_destructuring)) {
    initializer_error:
      return js_parse_error(s,
                            "a declaration in the head of a for-%s loop can't "
                            "have an initializer",
                            is_for_of ? "of" : "in");
    }
  } else {
    return js_parse_error(s, "expected 'of' or 'in' in for control expression");
  }
  if (next_token(s)) return -1;
  if (is_for_of) {
    if (js_parse_assign_expr(s, PF_IN_ACCEPTED)) return -1;
  } else {
    if (js_parse_expr(s)) return -1;
  }
  /* close the scope after having evaluated the expression so that
     the TDZ values are in the closures */
  close_scopes(s, s->cur_func->scope_level, block_scope_level);
  if (is_for_of) {
    if (is_async)
      emit_op(s, OP_for_await_of_start);
    else
      emit_op(s, OP_for_of_start);
    /* on stack: enum_rec */
  } else {
    emit_op(s, OP_for_in_start);
    /* on stack: enum_obj */
  }
  emit_goto(s, OP_goto, label_cont);

  if (js_parse_expect(s, ')')) return -1;

  if (OPTIMIZE) {
    /* move the `next` code here */
    DynBuf *bc = &s->cur_func->byte_code;
    int chunk_size = pos_expr - pos_next;
    int offset = bc->size - pos_next;
    int i;
    dbuf_realloc(bc, bc->size + chunk_size);
    dbuf_put(bc, bc->buf + pos_next, chunk_size);
    memset(bc->buf + pos_next, OP_nop, chunk_size);
    /* `next` part ends with a goto */
    s->cur_func->last_opcode_pos = bc->size - 5;
    /* relocate labels */
    for (i = label_cont; i < s->cur_func->label_count; i++) {
      LabelSlot *ls = &s->cur_func->label_slots[i];
      if (ls->pos >= pos_next && ls->pos < pos_expr) ls->pos += offset;
    }
  }

  emit_label(s, label_body);
  if (js_parse_statement(s)) return -1;

  close_scopes(s, s->cur_func->scope_level, block_scope_level);

  emit_label(s, label_cont);
  if (is_for_of) {
    if (is_async) {
      /* call the next method */
      emit_op(s, OP_dup3);
      emit_op(s, OP_drop);
      emit_op(s, OP_call_method);
      emit_u16(s, 0);
      /* get the result of the promise */
      emit_op(s, OP_await);
      /* unwrap the value and done values */
      emit_op(s, OP_iterator_get_value_done);
    } else {
      emit_op(s, OP_for_of_next);
      emit_u8(s, 0);
    }
  } else {
    emit_op(s, OP_for_in_next);
  }
  /* on stack: enum_rec / enum_obj value bool */
  emit_goto(s, OP_if_false, label_next);
  /* drop the undefined value from for_xx_next */
  emit_op(s, OP_drop);

  emit_label(s, label_break);
  if (is_for_of) {
    /* close and drop enum_rec */
    emit_op(s, OP_iterator_close);
  } else {
    emit_op(s, OP_drop);
  }
  pop_break_entry(s->cur_func);
  pop_scope(s);
  return 0;
}

static void set_eval_ret_undefined(JSParseState *s) {
  if (s->cur_func->eval_ret_idx >= 0) {
    emit_op(s, OP_undefined);
    emit_op(s, OP_put_loc);
    emit_u16(s, s->cur_func->eval_ret_idx);
  }
}

#ifdef ENABLE_QUICKJS_DEBUGGER
static void js_gen_debugger_statement(JSParseState *s, LEPUSContext *ctx) {
  LEPUSValue statement = JS_NewString_GC(ctx, "statement");
  HandleScope func_scope(s->ctx, &statement, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_IsException(statement)) return;
  if (emit_push_const(s, statement, 0) < 0) {
    return;
  }
  emit_op(s, OP_drop);
}
#endif

static __exception int js_parse_statement_or_decl(JSParseState *s,
                                                  int decl_mask) {
  LEPUSContext *ctx = s->ctx;
  HandleScope func_scope(ctx);
  JSAtom label_name;
  int tok;

  /* specific label handling */
  /* XXX: support multiple labels on loop statements */
  label_name = JS_ATOM_NULL;
  if (is_label(s)) {
    BlockEnv *be;

    label_name = s->token.u.ident.atom;

    for (be = s->cur_func->top_break; be; be = be->prev) {
      if (be->label_name == label_name) {
        js_parse_error(s, "duplicate label name");
        goto fail;
      }
    }

    if (next_token(s)) goto fail;
    if (js_parse_expect(s, ':')) goto fail;
    if (s->token.val != TOK_FOR && s->token.val != TOK_DO &&
        s->token.val != TOK_WHILE) {
      /* labelled regular statement */
      int label_break, mask;
      BlockEnv break_entry;

      label_break = new_label(s);
      push_break_entry(s->cur_func, &break_entry, label_name, label_break, -1,
                       0);
      if (!(s->cur_func->js_mode & JS_MODE_STRICT) &&
          (decl_mask & DECL_MASK_FUNC_WITH_LABEL)) {
        mask = DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL;
      } else {
        mask = 0;
      }
      if (js_parse_statement_or_decl(s, mask)) goto fail;
      emit_label(s, label_break);
      pop_break_entry(s->cur_func);
      goto done;
    }
  }

  switch (tok = s->token.val) {
    case '{':
      if (js_parse_block(s)) goto fail;
      break;
    case TOK_RETURN:
      if (s->cur_func->is_eval) {
        js_parse_error(s, "return not in a function");
        goto fail;
      }
      if (next_token(s)) goto fail;
      if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) {
        if (js_parse_expr(s)) goto fail;
        emit_return(s, TRUE);
      } else {
        emit_return(s, FALSE);
      }
      if (js_parse_expect_semi(s)) goto fail;
      break;
    case TOK_THROW:
      if (next_token(s)) goto fail;
      if (s->got_lf) {
        js_parse_error(s, "line terminator not allowed after throw");
        goto fail;
      }
      if (js_parse_expr(s)) goto fail;
      emit_op(s, OP_throw);
      if (js_parse_expect_semi(s)) goto fail;
      break;
    case TOK_LET:
    case TOK_CONST:
    haslet:
      if (!(decl_mask & DECL_MASK_OTHER)) {
        js_parse_error(
            s, "lexical declarations can't appear in single-statement context");
        goto fail;
      }
      /* fall thru */
    case TOK_VAR:
      if (next_token(s)) goto fail;
      if (js_parse_var(s, TRUE, tok, FALSE)) goto fail;
      if (js_parse_expect_semi(s)) goto fail;
      break;
    case TOK_IF: {
      int label1, label2, mask;
      if (next_token(s)) goto fail;
      /* create a new scope for `let f;if(1) function f(){}` */
      push_scope(s);
      set_eval_ret_undefined(s);
      if (js_parse_expr_paren(s)) goto fail;
      label1 = emit_goto(s, OP_if_false, -1);
      if (s->cur_func->js_mode & JS_MODE_STRICT)
        mask = 0;
      else
        mask = DECL_MASK_FUNC; /* Annex B.3.4 */

      if (js_parse_statement_or_decl(s, mask)) goto fail;

      if (s->token.val == TOK_ELSE) {
        label2 = emit_goto(s, OP_goto, -1);
        if (next_token(s)) goto fail;

        emit_label(s, label1);
        if (js_parse_statement_or_decl(s, mask)) goto fail;

        label1 = label2;
      }
      emit_label(s, label1);
      pop_scope(s);
    } break;
    case TOK_WHILE: {
      int label_cont, label_break;
      BlockEnv break_entry;

      label_cont = new_label(s);
      label_break = new_label(s);

      push_break_entry(s->cur_func, &break_entry, label_name, label_break,
                       label_cont, 0);

      if (next_token(s)) goto fail;

      set_eval_ret_undefined(s);

      emit_label(s, label_cont);
      if (js_parse_expr_paren(s)) goto fail;
      emit_goto(s, OP_if_false, label_break);

      if (js_parse_statement(s)) goto fail;
      emit_goto(s, OP_goto, label_cont);

      emit_label(s, label_break);

      pop_break_entry(s->cur_func);
    } break;
    case TOK_DO: {
      int label_cont, label_break, label1;
      BlockEnv break_entry;

      label_cont = new_label(s);
      label_break = new_label(s);
      label1 = new_label(s);

      push_break_entry(s->cur_func, &break_entry, label_name, label_break,
                       label_cont, 0);

      if (next_token(s)) goto fail;

      emit_label(s, label1);

      set_eval_ret_undefined(s);

      if (js_parse_statement(s)) goto fail;

      emit_label(s, label_cont);
      if (js_parse_expect(s, TOK_WHILE)) goto fail;
      if (js_parse_expr_paren(s)) goto fail;
      /* Insert semicolon if missing */
      if (s->token.val == ';') {
        if (next_token(s)) goto fail;
      }
      emit_goto(s, OP_if_true, label1);

      emit_label(s, label_break);

      pop_break_entry(s->cur_func);
    } break;
    case TOK_FOR: {
      int label_cont, label_break, label_body, label_test;
      int pos_cont, pos_body, block_scope_level;
      BlockEnv break_entry;
      int tok, bits;
      BOOL is_async;

      if (next_token(s)) goto fail;

      set_eval_ret_undefined(s);
      bits = 0;
      is_async = FALSE;
      if (s->token.val == '(') {
        js_parse_skip_parens_token(s, &bits, FALSE);
      } else if (s->token.val == TOK_AWAIT) {
        if (!(s->cur_func->func_kind & JS_FUNC_ASYNC)) {
          js_parse_error(s,
                         "for await is only valid in asynchronous functions");
          goto fail;
        }
        is_async = TRUE;
        if (next_token(s)) goto fail;
      }
      if (js_parse_expect(s, '(')) goto fail;

      if (!(bits & SKIP_HAS_SEMI)) {
        /* parse for/in or for/of */
        if (js_parse_for_in_of(s, label_name, is_async)) goto fail;
        break;
      }
      block_scope_level = s->cur_func->scope_level;

      /* create scope for the lexical variables declared in the initial,
         test and increment expressions */
      push_scope(s);
      /* initial expression */
      tok = s->token.val;
      if (tok != ';') {
        switch (is_let(s, DECL_MASK_OTHER)) {
          case TRUE:
            tok = TOK_LET;
            break;
          case FALSE:
            break;
          default:
            goto fail;
        }
        if (tok == TOK_VAR || tok == TOK_LET || tok == TOK_CONST) {
          if (next_token(s)) goto fail;
          if (js_parse_var(s, FALSE, tok, FALSE)) goto fail;
        } else {
          if (js_parse_expr2(s, FALSE)) goto fail;
          emit_op(s, OP_drop);
        }

        /* close the closures before the first iteration */
        close_scopes(s, s->cur_func->scope_level, block_scope_level);
      }
      if (js_parse_expect(s, ';')) goto fail;

      label_test = new_label(s);
      label_cont = new_label(s);
      label_body = new_label(s);
      label_break = new_label(s);

      push_break_entry(s->cur_func, &break_entry, label_name, label_break,
                       label_cont, 0);

      /* test expression */
      if (s->token.val == ';') {
        /* no test expression */
        label_test = label_body;
      } else {
        emit_label(s, label_test);
        if (js_parse_expr(s)) goto fail;
        emit_goto(s, OP_if_false, label_break);
      }
      if (js_parse_expect(s, ';')) goto fail;

      if (s->token.val == ')') {
        /* no end expression */
        break_entry.label_cont = label_cont = label_test;
        pos_cont = 0; /* avoid warning */
      } else {
        /* skip the end expression */
        emit_goto(s, OP_goto, label_body);

        pos_cont = s->cur_func->byte_code.size;
        emit_label(s, label_cont);
        if (js_parse_expr(s)) goto fail;
        emit_op(s, OP_drop);
        if (label_test != label_body) emit_goto(s, OP_goto, label_test);
      }
      if (js_parse_expect(s, ')')) goto fail;

      pos_body = s->cur_func->byte_code.size;
      emit_label(s, label_body);
      if (js_parse_statement(s)) goto fail;

      /* close the closures before the next iteration */
      /* XXX: check continue case */
      close_scopes(s, s->cur_func->scope_level, block_scope_level);

      if (OPTIMIZE && label_test != label_body && label_cont != label_test) {
        /* move the increment code here */
        DynBuf *bc = &s->cur_func->byte_code;
        int chunk_size = pos_body - pos_cont;
        int offset = bc->size - pos_cont;
        int i;
        dbuf_realloc(bc, bc->size + chunk_size);
        dbuf_put(bc, bc->buf + pos_cont, chunk_size);
        memset(bc->buf + pos_cont, OP_nop, chunk_size);
        /* increment part ends with a goto */
        s->cur_func->last_opcode_pos = bc->size - 5;
        /* relocate labels */
        for (i = label_cont; i < s->cur_func->label_count; i++) {
          LabelSlot *ls = &s->cur_func->label_slots[i];
          if (ls->pos >= pos_cont && ls->pos < pos_body) ls->pos += offset;
        }
      } else {
        emit_goto(s, OP_goto, label_cont);
      }

      emit_label(s, label_break);

      pop_break_entry(s->cur_func);
      pop_scope(s);
    } break;
    case TOK_BREAK:
    case TOK_CONTINUE: {
      int is_cont = s->token.val - TOK_BREAK;
      int label;

      if (next_token(s)) goto fail;
      if (!s->got_lf && s->token.val == TOK_IDENT &&
          !s->token.u.ident.is_reserved)
        label = s->token.u.ident.atom;
      else
        label = JS_ATOM_NULL;
      if (emit_break(s, label, is_cont)) goto fail;
      if (label != JS_ATOM_NULL) {
        if (next_token(s)) goto fail;
      }
      if (js_parse_expect_semi(s)) goto fail;
    } break;
    case TOK_SWITCH: {
      int label_case, label_break, label1;
      int default_label_pos;
      BlockEnv break_entry;

      if (next_token(s)) goto fail;

      set_eval_ret_undefined(s);
      if (js_parse_expr_paren(s)) goto fail;

      push_scope(s);
      label_break = new_label(s);
      push_break_entry(s->cur_func, &break_entry, label_name, label_break, -1,
                       1);

      if (js_parse_expect(s, '{')) goto fail;

      default_label_pos = -1;
      label_case = -1;
      while (s->token.val != '}') {
        if (s->token.val == TOK_CASE) {
          label1 = -1;
          if (label_case >= 0) {
            /* skip the case if needed */
            label1 = emit_goto(s, OP_goto, -1);
          }
          emit_label(s, label_case);
          label_case = -1;
          for (;;) {
            /* parse a sequence of case clauses */
            if (next_token(s)) goto fail;
            emit_op(s, OP_dup);
            if (js_parse_expr(s)) goto fail;
            if (js_parse_expect(s, ':')) goto fail;
            emit_op(s, OP_strict_eq);
            if (s->token.val == TOK_CASE) {
              label1 = emit_goto(s, OP_if_true, label1);
            } else {
              label_case = emit_goto(s, OP_if_false, -1);
              emit_label(s, label1);
              break;
            }
          }
        } else if (s->token.val == TOK_DEFAULT) {
          if (next_token(s)) goto fail;
          if (js_parse_expect(s, ':')) goto fail;
          if (default_label_pos >= 0) {
            js_parse_error(s, "duplicate default");
            goto fail;
          }
          if (label_case < 0) {
            /* falling thru direct from switch expression */
            label_case = emit_goto(s, OP_goto, -1);
          }
          /* Emit a dummy label opcode. Label will be patched after
             the end of the switch body. Do not use emit_label(s, 0)
             because it would clobber label 0 address, preventing
             proper optimizer operation.
           */
          emit_op(s, OP_label);
          emit_u32(s, 0);
          default_label_pos = s->cur_func->byte_code.size - 4;
        } else {
          if (label_case < 0) {
            /* falling thru direct from switch expression */
            js_parse_error(s, "invalid switch statement");
            goto fail;
          }
          if (js_parse_statement_or_decl(s, DECL_MASK_ALL)) goto fail;
        }
      }
      if (js_parse_expect(s, '}')) goto fail;
      if (default_label_pos >= 0) {
        /* Ugly patch for the the `default` label, shameful and risky */
        put_u32(s->cur_func->byte_code.buf + default_label_pos, label_case);
        s->cur_func->label_slots[label_case].pos = default_label_pos + 4;
      } else {
        emit_label(s, label_case);
      }
      emit_label(s, label_break);
      emit_op(s, OP_drop); /* drop the switch expression */

      pop_break_entry(s->cur_func);
      pop_scope(s);
    } break;
    case TOK_TRY: {
      int label_catch, label_catch2, label_finally, label_end;
      JSAtom name;
      BlockEnv block_env;

      set_eval_ret_undefined(s);
      if (next_token(s)) goto fail;
      label_catch = new_label(s);
      label_catch2 = new_label(s);
      label_finally = new_label(s);
      label_end = new_label(s);

      emit_goto(s, OP_catch, label_catch);

      push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL, -1, -1, 1);
      block_env.label_finally = label_finally;

      if (js_parse_block(s)) goto fail;

      pop_break_entry(s->cur_func);

      if (js_is_live_code(s)) {
        /* drop the catch offset */
        emit_op(s, OP_drop);
        /* must push dummy value to keep same stack size */
        emit_op(s, OP_undefined);
        emit_goto(s, OP_gosub, label_finally);
        emit_op(s, OP_drop);

        emit_goto(s, OP_goto, label_end);
      }

      if (s->token.val == TOK_CATCH) {
        if (next_token(s)) goto fail;

        push_scope(s); /* catch variable */
        emit_label(s, label_catch);

        if (s->token.val == '{') {
          /* support optional-catch-binding feature */
          emit_op(s, OP_drop); /* pop the exception object */
        } else {
          if (js_parse_expect(s, '(')) goto fail;
          if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)) {
            if (s->token.val == '[' || s->token.val == '{') {
              /* XXX: TOK_LET is not completely correct */
              if (js_parse_destructing_element_GC(s, TOK_LET, 0, TRUE, -1,
                                                  TRUE) < 0)
                goto fail;
            } else {
              js_parse_error(s, "identifier expected");
              goto fail;
            }
          } else {
            HandleScope func_scope(ctx->rt);
            name = s->token.u.ident.atom;
            func_scope.PushLEPUSAtom(name);
            if (next_token(s) || js_define_var(s, name, TOK_CATCH) < 0) {
              goto fail;
            }
            /* store the exception value in the catch variable */
            emit_op(s, OP_scope_put_var);
            emit_u32(s, name);
            emit_u16(s, s->cur_func->scope_level);
          }
          if (js_parse_expect(s, ')')) goto fail;
        }
        /* XXX: should keep the address to nop it out if there is no finally
         * block */
        emit_goto(s, OP_catch, label_catch2);

        push_scope(s); /* catch block */
        push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL, -1, -1, 1);
        block_env.label_finally = label_finally;

        if (js_parse_block(s)) goto fail;

        pop_break_entry(s->cur_func);
        pop_scope(s); /* catch block */
        pop_scope(s); /* catch variable */

        if (js_is_live_code(s)) {
          /* drop the catch2 offset */
          emit_op(s, OP_drop);
          /* XXX: should keep the address to nop it out if there is no finally
           * block */
          /* must push dummy value to keep same stack size */
          emit_op(s, OP_undefined);
          emit_goto(s, OP_gosub, label_finally);
          emit_op(s, OP_drop);
          emit_goto(s, OP_goto, label_end);
        }
        /* catch exceptions thrown in the catch block to execute the
         * finally clause and rethrow the exception */
        emit_label(s, label_catch2);
        /* catch value is at TOS, no need to push undefined */
        emit_goto(s, OP_gosub, label_finally);
        emit_op(s, OP_throw);

      } else if (s->token.val == TOK_FINALLY) {
        /* finally without catch : execute the finally clause
         * and rethrow the exception */
        emit_label(s, label_catch);
        /* catch value is at TOS, no need to push undefined */
        emit_goto(s, OP_gosub, label_finally);
        emit_op(s, OP_throw);
      } else {
        js_parse_error(s, "expecting catch or finally");
        goto fail;
      }
      emit_label(s, label_finally);
      if (s->token.val == TOK_FINALLY) {
        int saved_eval_ret_idx;
        if (next_token(s)) goto fail;
        /* on the stack: ret_value gosub_ret_value */
        push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL, -1, -1, 2);
        if (s->cur_func->eval_ret_idx >= 0) {
          /* 'finally' updates eval_ret only if not a normal
             termination */
          saved_eval_ret_idx = add_var(s->ctx, s->cur_func, JS_ATOM__ret_);
          if (saved_eval_ret_idx < 0) goto fail;
          emit_op(s, OP_get_loc);
          emit_u16(s, s->cur_func->eval_ret_idx);
          emit_op(s, OP_put_loc);
          emit_u16(s, saved_eval_ret_idx);
          set_eval_ret_undefined(s);
        }

        if (js_parse_block(s)) goto fail;

        if (s->cur_func->eval_ret_idx >= 0) {
          emit_op(s, OP_get_loc);
          emit_u16(s, saved_eval_ret_idx);
          emit_op(s, OP_put_loc);
          emit_u16(s, s->cur_func->eval_ret_idx);
        }
        pop_break_entry(s->cur_func);
      }
      emit_op(s, OP_ret);
      emit_label(s, label_end);
    } break;
    case ';':
      /* empty statement */
      if (next_token(s)) goto fail;
      break;
    case TOK_WITH:
      if (s->cur_func->js_mode & JS_MODE_STRICT) {
        js_parse_error(s, "invalid keyword: with");
        goto fail;
      } else {
        int with_idx;

        if (next_token(s)) goto fail;

        if (js_parse_expr_paren(s)) goto fail;

        push_scope(s);
        with_idx =
            define_var_GC(s, s->cur_func, JS_ATOM__with_, JS_VAR_DEF_WITH);
        if (with_idx < 0) goto fail;
        emit_op(s, OP_to_object);
        emit_op(s, OP_put_loc);
        emit_u16(s, with_idx);

        set_eval_ret_undefined(s);
        if (js_parse_statement(s)) goto fail;

        /* Popping scope drops lexical context for the with object variable */
        pop_scope(s);
      }
      break;
    case TOK_FUNCTION:
      /* ES6 Annex B.3.2 and B.3.3 semantics */
      if (!(decl_mask & DECL_MASK_FUNC)) goto func_decl_error;
      if (!(decl_mask & DECL_MASK_OTHER) && peek_token(s, FALSE) == '*')
        goto func_decl_error;
      goto parse_func_var;
    case TOK_IDENT:
      if (s->token.u.ident.is_reserved) {
        js_parse_error_reserved_identifier(s);
        goto fail;
      }
      /* Determine if `let` introduces a Declaration or an ExpressionStatement
       */
      switch (is_let(s, decl_mask)) {
        case TRUE:
          tok = TOK_LET;
          goto haslet;
        case FALSE:
          break;
        default:
          goto fail;
      }
      if (token_is_pseudo_keyword(s, JS_ATOM_async) &&
          peek_token(s, TRUE) == TOK_FUNCTION) {
        if (!(decl_mask & DECL_MASK_OTHER)) {
        func_decl_error:
          js_parse_error(s,
                         "function declarations can't appear in "
                         "single-statement context");
          goto fail;
        }
      parse_func_var:
        if (js_parse_function_decl(s, JS_PARSE_FUNC_VAR, JS_FUNC_NORMAL,
                                   JS_ATOM_NULL, s->token.ptr,
                                   s->token.line_num))
          goto fail;
        break;
      }
      goto hasexpr;

    case TOK_CLASS:
      if (!(decl_mask & DECL_MASK_OTHER)) {
        js_parse_error(
            s, "class declarations can't appear in single-statement context");
        goto fail;
      }
      if (js_parse_class(s, FALSE, JS_PARSE_EXPORT_NONE)) return -1;
      break;

    case TOK_DEBUGGER:
#ifdef ENABLE_QUICKJS_DEBUGGER
      if (next_token(s)) goto fail;
      // generate opcode: op_push_const
      LEPUSValue debugger;
      debugger = JS_NewString_GC(ctx, "debugger");
      func_scope.PushHandle(&debugger, HANDLE_TYPE_LEPUS_VALUE);
      if (LEPUS_IsException(debugger)) goto fail;
      if (emit_push_const(s, debugger, 0) < 0) {
        goto fail;
      }
      emit_op(s, OP_drop);
      func_scope.ResetHandle(&debugger, HANDLE_TYPE_LEPUS_VALUE);
      break;
#endif
    case TOK_ENUM:
    case TOK_EXPORT:
    case TOK_EXTENDS:
      js_unsupported_keyword(s, s->token.u.ident.atom);
      goto fail;

    default:
    hasexpr:
      if (js_parse_expr(s)) goto fail;
      if (s->cur_func->eval_ret_idx >= 0) {
        /* store the expression value so that it can be returned
           by eval() */
        emit_op(s, OP_put_loc);
        emit_u16(s, s->cur_func->eval_ret_idx);
      } else {
        emit_op(s, OP_drop); /* drop the result */
      }
      if (js_parse_expect_semi(s)) goto fail;
      break;
  }
done:
#ifdef ENABLE_QUICKJS_DEBUGGER
  if (ctx->debugger_mode) {
    js_gen_debugger_statement(s, s->ctx);
  }
#endif
  return 0;
fail:
  return -1;
}
#endif

#ifndef NO_QUICKJS_COMPILER
/* Unfortunately, the spec gives a different behavior from GetOwnProperty ! */
QJS_STATIC int js_module_ns_has(LEPUSContext *ctx, LEPUSValueConst obj,
                                JSAtom atom) {
  return (find_own_property1(LEPUS_VALUE_GET_OBJ(obj), atom) != NULL);
}

static __exception int js_parse_source_element(JSParseState *s) {
  JSFunctionDef *fd = s->cur_func;
  int tok;

  if (s->token.val == TOK_FUNCTION ||
      (token_is_pseudo_keyword(s, JS_ATOM_async) &&
       peek_token(s, TRUE) == TOK_FUNCTION)) {
    if (js_parse_function_decl(s, JS_PARSE_FUNC_STATEMENT, JS_FUNC_NORMAL,
                               JS_ATOM_NULL, s->token.ptr, s->token.line_num))
      return -1;
  } else if (s->token.val == TOK_EXPORT && fd->module) {
    if (js_parse_export(s)) return -1;
  } else if (s->token.val == TOK_IMPORT && fd->module &&
             ((tok = peek_token(s, FALSE)) != '(' && tok != '.')) {
    /* the peek_token is needed to avoid confusion with ImportCall
       (dynamic import) */
    if (js_parse_import(s)) return -1;
  } else {
    if (js_parse_statement_or_decl(s, DECL_MASK_ALL)) return -1;
  }
  return 0;
}

JSFunctionDef *js_new_function_def_GC(LEPUSContext *ctx, JSFunctionDef *parent,
                                      BOOL is_eval, BOOL is_func_expr,
                                      const char *filename, int line_num) {
  JSFunctionDef *fd;

  fd = static_cast<JSFunctionDef *>(
      lepus_mallocz(ctx, sizeof(*fd), ALLOC_TAG_JSFunctionDef));
  if (!fd) return NULL;
  HandleScope func_scope(ctx, fd, HANDLE_TYPE_DIR_HEAP_OBJ);

  fd->ctx = ctx;
  init_list_head(&fd->child_list);

  /* insert in parent list */
  fd->parent = parent;
  fd->parent_cpool_idx = -1;
  if (parent) {
    list_add_tail(&fd->link, &parent->child_list);
    fd->js_mode = parent->js_mode;
    fd->parent_scope_level = parent->scope_level;
  }

  fd->is_eval = is_eval;
  fd->is_func_expr = is_func_expr;
  js_dbuf_init(ctx, &fd->byte_code);
  fd->last_opcode_pos = -1;
  fd->func_name = JS_ATOM_NULL;
  fd->var_object_idx = -1;
  fd->arg_var_object_idx = -1;
  fd->arguments_var_idx = -1;
  fd->arguments_arg_idx = -1;
  fd->func_var_idx = -1;
  fd->eval_ret_idx = -1;
  fd->this_var_idx = -1;
  fd->new_target_var_idx = -1;
  fd->this_active_func_var_idx = -1;
  fd->home_object_var_idx = -1;

  /* XXX: should distinguish arg, var and var object and body scopes */
  fd->scope_level = 0; /* 0: var/arg scope, 1:body scope */
  fd->scope_first = -1;
  fd->body_scope = -1;
  fd->scopes = fd->def_scope_array;
  fd->scope_size = countof(fd->def_scope_array);
  fd->scope_count = 1;
  fd->scopes[0].first = -1;
  fd->scopes[0].parent = -1;

  fd->filename = LEPUS_NewAtom(ctx, filename);
  fd->line_num = line_num;

  js_dbuf_init(ctx, &fd->pc2line);
  // fd->pc2line_last_line_num = line_num;
  // fd->pc2line_last_pc = 0;
  fd->last_opcode_line_num = line_num;

  return fd;
}
#endif

#ifndef NO_QUICKJS_COMPILER
/* func_name must be JS_ATOM_NULL for JS_PARSE_FUNC_STATEMENT and
   JS_PARSE_FUNC_EXPR, JS_PARSE_FUNC_ARROW and JS_PARSE_FUNC_VAR */
__exception int js_parse_function_decl2_GC(
    JSParseState *s, JSParseFunctionEnum func_type,
    JSFunctionKindEnum func_kind, JSAtom func_name, const uint8_t *ptr,
    int function_line_num, JSParseExportEnum export_flag, JSFunctionDef **pfd) {
  LEPUSContext *ctx = s->ctx;
  JSFunctionDef *fd = s->cur_func;
  HandleScope func_scope(ctx, &fd, HANDLE_TYPE_HEAP_OBJ);
  BOOL is_expr;
  int func_idx, lexical_func_idx = -1;
  BOOL has_opt_arg;
  BOOL create_func_var = FALSE;

  is_expr =
      (func_type != JS_PARSE_FUNC_STATEMENT && func_type != JS_PARSE_FUNC_VAR);

  if (func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR ||
      func_type == JS_PARSE_FUNC_EXPR) {
    if (func_kind == JS_FUNC_NORMAL &&
        token_is_pseudo_keyword(s, JS_ATOM_async) &&
        peek_token(s, TRUE) != '\n') {
      if (next_token(s)) return -1;
      func_kind = JS_FUNC_ASYNC;
    }
    if (next_token(s)) return -1;
    if (s->token.val == '*') {
      if (next_token(s)) return -1;
      func_kind =
          static_cast<JSFunctionKindEnum>(func_kind | JS_FUNC_GENERATOR);
    }

    if (s->token.val == TOK_IDENT) {
      if (s->token.u.ident.is_reserved ||
          (s->token.u.ident.atom == JS_ATOM_yield &&
           func_type == JS_PARSE_FUNC_EXPR &&
           (func_kind & JS_FUNC_GENERATOR)) ||
          (s->token.u.ident.atom == JS_ATOM_await &&
           func_type == JS_PARSE_FUNC_EXPR && (func_kind & JS_FUNC_ASYNC))) {
        return js_parse_error_reserved_identifier(s);
      }
    }
    if (s->token.val == TOK_IDENT ||
        (((s->token.val == TOK_YIELD && !(fd->js_mode & JS_MODE_STRICT)) ||
          (s->token.val == TOK_AWAIT && !s->is_module)) &&
         func_type == JS_PARSE_FUNC_EXPR)) {
      func_name = s->token.u.ident.atom;
      if (next_token(s)) {
        return -1;
      }
    } else {
      if (func_type != JS_PARSE_FUNC_EXPR &&
          export_flag != JS_PARSE_EXPORT_DEFAULT) {
        return js_parse_error(s, "function name expected");
      }
    }
  }

  if (fd->is_eval && fd->eval_type == LEPUS_EVAL_TYPE_MODULE &&
      (func_type == JS_PARSE_FUNC_STATEMENT ||
       func_type == JS_PARSE_FUNC_VAR)) {
    JSHoistedDef *hf;
    hf = find_hoisted_def(fd, func_name);
    /* XXX: should check scope chain */
    if (hf && hf->scope_level == fd->scope_level) {
      js_parse_error(
          s, "invalid redefinition of global identifier in module code");
      return -1;
    }
  }

  if (func_type == JS_PARSE_FUNC_VAR) {
    /* Create lexical name here so function closure contains it */
    if (!(fd->js_mode & JS_MODE_STRICT) && func_kind == JS_FUNC_NORMAL &&
        find_lexical_decl(ctx, fd, func_name, fd->scope_first, FALSE) < 0 &&
        !((func_idx = find_var(ctx, fd, func_name)) >= 0 &&
          (func_idx & ARGUMENT_VAR_OFFSET)) &&
        !(func_name == JS_ATOM_arguments && fd->has_arguments_binding)) {
      create_func_var = TRUE;
    }
    if (fd->is_eval &&
        (fd->eval_type == LEPUS_EVAL_TYPE_GLOBAL ||
         fd->eval_type == LEPUS_EVAL_TYPE_MODULE) &&
        fd->scope_level == fd->body_scope) {
      /* avoid creating a lexical variable in the global
         scope. XXX: check annex B */
      JSHoistedDef *hf;
      hf = find_hoisted_def(fd, func_name);
      /* XXX: should check scope chain */
      if (hf && hf->scope_level == fd->scope_level) {
        js_parse_error(s, "invalid redefinition of global identifier");
        return -1;
      }
    } else {
      /* Always create a lexical name, fail if at the same scope as
         existing name */
      /* Lexical variable will be initialized upon entering scope */
      lexical_func_idx = define_var_GC(s, fd, func_name,
                                       func_kind != JS_FUNC_NORMAL
                                           ? JS_VAR_DEF_NEW_FUNCTION_DECL
                                           : JS_VAR_DEF_FUNCTION_DECL);
      if (lexical_func_idx < 0) {
        return -1;
      }
    }
  }

  fd = js_new_function_def_GC(ctx, fd, FALSE, is_expr, s->filename,
                              function_line_num);
  if (!fd) {
    return -1;
  }
#ifdef ENABLE_QUICKJS_DEBUGGER
  fd->column_num = compute_column(s, 0);
#endif
  if (pfd) *pfd = fd;
  s->cur_func = fd;
  fd->func_name = func_name;
  /* XXX: test !fd->is_generator is always false */
  fd->has_prototype =
      (func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR ||
       func_type == JS_PARSE_FUNC_EXPR) &&
      func_kind == JS_FUNC_NORMAL;
  fd->has_home_object =
      (func_type == JS_PARSE_FUNC_METHOD || func_type == JS_PARSE_FUNC_GETTER ||
       func_type == JS_PARSE_FUNC_SETTER ||
       func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR ||
       func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR);
  fd->has_arguments_binding = (func_type != JS_PARSE_FUNC_ARROW);
  fd->has_this_binding = fd->has_arguments_binding;
  fd->is_derived_class_constructor =
      (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR);
  if (func_type == JS_PARSE_FUNC_ARROW) {
    fd->new_target_allowed = fd->parent->new_target_allowed;
    fd->super_call_allowed = fd->parent->super_call_allowed;
    fd->super_allowed = fd->parent->super_allowed;
    fd->arguments_allowed = fd->parent->arguments_allowed;
  } else {
    fd->new_target_allowed = TRUE;
    fd->super_call_allowed = fd->is_derived_class_constructor;
    fd->super_allowed = fd->has_home_object;
    fd->arguments_allowed = TRUE;
  }

  /* fd->in_function_body == FALSE prevents yield/await during the parsing
     of the arguments in generator/async functions. They are parsed as
     regular identifiers for other function kinds. */
  fd->func_kind = func_kind;
  fd->func_type = func_type;
  fd->src_start = (const char *)ptr;

  if (func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR ||
      func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR) {
    /* error if not invoked as a constructor */
    emit_op(s, OP_check_ctor);
  }

  if (func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR) {
    emit_class_field_init(s);
  }

  /* parse arguments */
  fd->has_simple_parameter_list = TRUE;
  fd->has_parameter_expressions = FALSE;
  has_opt_arg = FALSE;
  if (func_type == JS_PARSE_FUNC_ARROW && s->token.val == TOK_IDENT) {
    JSAtom name;
    if (s->token.u.ident.is_reserved) {
      js_parse_error_reserved_identifier(s);
      goto fail;
    }
    name = s->token.u.ident.atom;
    if (add_arg(ctx, fd, name) < 0) goto fail;
    fd->defined_arg_count = 1;
  } else {
    if (s->token.val == '(') {
      int skip_bits;
      BOOL has_ellipsis = FALSE;
      js_parse_skip_parens_token(s, &skip_bits, FALSE, &has_ellipsis);
      // rest parameters can not be used in setters
      // https://262.ecma-international.org/6.0/#sec-function-definitions
      if (has_ellipsis == TRUE && func_type == JS_PARSE_FUNC_SETTER) {
        LEPUS_ThrowSyntaxError(
            ctx, "Setter function argument must not be a rest parameter");
        goto fail;
      }
      if (skip_bits & SKIP_HAS_ASSIGNMENT) fd->has_parameter_expressions = TRUE;
      if (next_token(s)) goto fail;
    } else {
      if (js_parse_expect(s, '(')) goto fail;
    }

    if (fd->has_parameter_expressions) {
      fd->scope_level = -1; /* force no parent scope */
      if (push_scope(s) < 0) return -1;
    }

    while (s->token.val != ')') {
      JSAtom name;
      BOOL rest = FALSE;
      int idx, has_initializer;

      if (s->token.val == TOK_ELLIPSIS) {
        fd->has_simple_parameter_list = FALSE;
        rest = TRUE;
        if (next_token(s)) goto fail;
      }
      if (s->token.val == '[' || s->token.val == '{') {
        fd->has_simple_parameter_list = FALSE;
        if (rest) {
          emit_op(s, OP_rest);
          emit_u16(s, fd->arg_count);
        } else {
          /* unnamed arg for destructuring */
          idx = add_arg(ctx, fd, JS_ATOM_NULL);
          emit_op(s, OP_get_arg);
          emit_u16(s, idx);
        }
        has_initializer = js_parse_destructing_element_GC(
            s, fd->has_parameter_expressions ? TOK_LET : TOK_VAR, 1, TRUE, -1,
            TRUE);
        if (has_initializer < 0) goto fail;
        if (has_initializer) has_opt_arg = TRUE;
        if (!has_opt_arg) fd->defined_arg_count++;
      } else if (s->token.val == TOK_IDENT) {
        if (s->token.u.ident.is_reserved) {
          js_parse_error_reserved_identifier(s);
          goto fail;
        }
        name = s->token.u.ident.atom;
        if (name == JS_ATOM_yield && fd->func_kind == JS_FUNC_GENERATOR) {
          js_parse_error_reserved_identifier(s);
          goto fail;
        }
        if (fd->has_parameter_expressions) {
          if (define_var_GC(s, fd, name, JS_VAR_DEF_LET) < 0) goto fail;
        }
        idx = add_arg(ctx, fd, name);
        if (idx < 0) goto fail;
        if (next_token(s)) goto fail;
        if (rest) {
          emit_op(s, OP_rest);
          emit_u16(s, idx);
          if (fd->has_parameter_expressions) {
            emit_op(s, OP_dup);
            emit_op(s, OP_scope_put_var_init);
            emit_atom(s, name);
            emit_u16(s, fd->scope_level);
          }
          emit_op(s, OP_put_arg);
          emit_u16(s, idx);
          fd->has_simple_parameter_list = FALSE;
          has_opt_arg = TRUE;
        } else if (s->token.val == '=') {
          fd->has_simple_parameter_list = FALSE;
          has_opt_arg = TRUE;

          if (next_token(s)) goto fail;

#if 0
                    /* XXX: not correct for eval code */
                    /* Check for a default value of `undefined`
                       to omit default argument processing */
                    if (s->token.val == TOK_IDENT &&
                        s->token.u.ident.atom == JS_ATOM_undefined &&
                        fd->parent == NULL &&
                        ((tok = peek_token(s, FALSE)) == ',' || tok == ')')) {
                        if (next_token(s))  /* ignore undefined token */
                            goto fail;
                    } else
#endif

          int label = new_label(s);
          emit_op(s, OP_get_arg);
          emit_u16(s, idx);
          emit_op(s, OP_dup);
          emit_op(s, OP_undefined);
          emit_op(s, OP_strict_eq);
          emit_goto(s, OP_if_false, label);
          emit_op(s, OP_drop);
          if (js_parse_assign_expr(s, PF_IN_ACCEPTED)) goto fail;
          set_object_name(s, name);
          emit_op(s, OP_dup);
          emit_op(s, OP_put_arg);
          emit_u16(s, idx);
          emit_label(s, label);
          emit_op(s, OP_scope_put_var_init);
          emit_atom(s, name);
          emit_u16(s, fd->scope_level);
        } else {
          if (!has_opt_arg) {
            fd->defined_arg_count++;
          }
          if (fd->has_parameter_expressions) {
            /* copy the argument to the argument scope */
            emit_op(s, OP_get_arg);
            emit_u16(s, idx);
            emit_op(s, OP_scope_put_var_init);
            emit_atom(s, name);
            emit_u16(s, fd->scope_level);
          }
        }
      } else {
        js_parse_error(s, "missing formal parameter");
        goto fail;
      }
      if (rest && s->token.val != ')') {
        js_parse_expect(s, ')');
        goto fail;
      }
      if (s->token.val == ')') break;
      if (js_parse_expect(s, ',')) goto fail;
    }
    if ((func_type == JS_PARSE_FUNC_GETTER && fd->arg_count != 0) ||
        (func_type == JS_PARSE_FUNC_SETTER && fd->arg_count != 1)) {
      js_parse_error(s, "invalid number of arguments for getter or setter");
      goto fail;
    }
  }

  if (fd->has_parameter_expressions) {
    int idx;

    /* Copy the variables in the argument scope to the variable
       scope (see FunctionDeclarationInstantiation() in spec). The
       normal arguments are already present, so no need to copy
       them. */
    idx = fd->scopes[fd->scope_level].first;
    while (idx >= 0) {
      JSVarDef *vd = &fd->vars[idx];
      if (vd->scope_level != fd->scope_level) break;
      if (find_var(ctx, fd, vd->var_name) < 0) {
        if (add_var(ctx, fd, vd->var_name) < 0) goto fail;
        vd = &fd->vars[idx]; /* fd->vars may have been reallocated */
        emit_op(s, OP_scope_get_var);
        emit_atom(s, vd->var_name);
        emit_u16(s, fd->scope_level);
        emit_op(s, OP_scope_put_var);
        emit_atom(s, vd->var_name);
        emit_u16(s, 0);
      }
      idx = vd->scope_next;
    }

    /* the argument scope has no parent, hence we don't use pop_scope(s) */
    emit_op(s, OP_leave_scope);
    emit_u16(s, fd->scope_level);

    /* set the variable scope as the current scope */
    fd->scope_level = 0;
    fd->scope_first = fd->scopes[fd->scope_level].first;
  }

  if (next_token(s)) goto fail;

  /* generator function: yield after the parameters are evaluated */
  if (func_kind == JS_FUNC_GENERATOR || func_kind == JS_FUNC_ASYNC_GENERATOR)
    emit_op(s, OP_initial_yield);

  /* in generators, yield expression is forbidden during the parsing
     of the arguments */
  fd->in_function_body = TRUE;
  push_scope(s); /* enter body scope: fd->scope_level = 1 */
  fd->body_scope = fd->scope_level;

  if (s->token.val == TOK_ARROW) {
    if (next_token(s)) goto fail;

    if (s->token.val != '{') {
      if (js_parse_function_check_names(s, fd, func_name)) goto fail;

      if (js_parse_assign_expr(s, PF_IN_ACCEPTED)) goto fail;

      if (func_kind != JS_FUNC_NORMAL)
        emit_op(s, OP_return_async);
      else
        emit_op(s, OP_return);

      if (!(fd->js_mode & JS_MODE_STRIP)) {
        /* save the function source code */
        /* the end of the function source code is after the last
           token of the function source stored into s->last_ptr */
        fd->source_len = s->last_ptr - ptr;
        fd->source = js_strmalloc((const char *)ptr, fd->source_len);
        if (!fd->source) goto fail;
      }
      goto done;
    }
  }

  if (js_parse_expect(s, '{')) goto fail;

  if (js_parse_directives(s)) goto fail;

  /* in strict_mode, check function and argument names */
  if (js_parse_function_check_names(s, fd, func_name)) goto fail;

  while (s->token.val != '}') {
    if (js_parse_source_element(s)) goto fail;
  }
  if (!(fd->js_mode & JS_MODE_STRIP)) {
    /* save the function source code */
    fd->source_len = s->buf_ptr - ptr;
    fd->source = js_strmalloc((const char *)ptr, fd->source_len);
    if (!fd->source) goto fail;
  }

  if (next_token(s)) {
    /* consume the '}' */
    goto fail;
  }

  /* in case there is no return, add one */
  if (js_is_live_code(s)) {
    emit_return(s, FALSE);
  }
done:
  s->cur_func = fd->parent;

  /* create the function object */
  {
    int idx;
    JSAtom func_name = fd->func_name;

    /* the real object will be set at the end of the compilation */
    idx = cpool_add(s, LEPUS_NULL);
    fd->parent_cpool_idx = idx;

    if (is_expr) {
      /* for constructors, no code needs to be generated here */
      if (func_type != JS_PARSE_FUNC_CLASS_CONSTRUCTOR &&
          func_type != JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR) {
        /* OP_fclosure creates the function object from the bytecode
           and adds the scope information */
        emit_op(s, OP_fclosure);
        emit_u32(s, idx);
        if (func_name == JS_ATOM_NULL) {
          emit_op(s, OP_set_name);
          emit_u32(s, JS_ATOM_NULL);
        }
      }
    } else if (func_type == JS_PARSE_FUNC_VAR) {
      emit_op(s, OP_fclosure);
      emit_u32(s, idx);
      if (create_func_var) {
        if (s->cur_func->is_global_var) {
          JSHoistedDef *hf;
          /* the global variable must be defined at the start of the
             function */
          hf = add_hoisted_def(ctx, s->cur_func, -1, func_name, -1, FALSE);
          if (!hf) goto fail;
          hf->scope_level = 0;
          hf->force_init = ((s->cur_func->js_mode & JS_MODE_STRICT) != 0);
          /* store directly into global var, bypass lexical scope */
          emit_op(s, OP_dup);
          emit_op(s, OP_scope_put_var);
          emit_atom(s, func_name);
          emit_u16(s, 0);
        } else {
          /* do not call define_var to bypass lexical scope check */
          func_idx = find_var(ctx, s->cur_func, func_name);
          if (func_idx < 0) {
            func_idx = add_var(ctx, s->cur_func, func_name);
            if (func_idx < 0) goto fail;
          }
          /* store directly into local var, bypass lexical catch scope */
          emit_op(s, OP_dup);
          emit_op(s, OP_scope_put_var);
          emit_atom(s, func_name);
          emit_u16(s, 0);
        }
      }
      if (lexical_func_idx >= 0) {
        /* lexical variable will be initialized upon entering scope */
        s->cur_func->vars[lexical_func_idx].func_pool_idx = idx;
        emit_op(s, OP_drop);
      } else {
        /* store function object into its lexical name */
        /* XXX: could use OP_put_loc directly */
        emit_op(s, OP_scope_put_var_init);
        emit_atom(s, func_name);
        emit_u16(s, s->cur_func->scope_level);
      }
    } else {
      if (!s->cur_func->is_global_var) {
        int var_idx = define_var_GC(s, s->cur_func, func_name, JS_VAR_DEF_VAR);

        if (var_idx < 0) goto fail;
        /* the variable will be assigned at the top of the function */
        if (var_idx & ARGUMENT_VAR_OFFSET) {
          s->cur_func->args[var_idx - ARGUMENT_VAR_OFFSET].func_pool_idx = idx;
        } else {
          s->cur_func->vars[var_idx].func_pool_idx = idx;
        }
      } else {
        JSAtom func_var_name;
        JSHoistedDef *hf;
        if (func_name == JS_ATOM_NULL)
          func_var_name = JS_ATOM__default_; /* export default */
        else
          func_var_name = func_name;
        /* the variable will be assigned at the top of the function */
        hf = add_hoisted_def(ctx, s->cur_func, idx, func_var_name, -1, FALSE);
        if (!hf) goto fail;
        hf->cpool_idx = idx;
        if (export_flag != JS_PARSE_EXPORT_NONE) {
          if (!add_export_entry(s, s->cur_func->module, func_var_name,
                                export_flag == JS_PARSE_EXPORT_NAMED
                                    ? func_var_name
                                    : JS_ATOM_default,
                                JS_EXPORT_TYPE_LOCAL))
            goto fail;
        }
      }
    }
  }
  return 0;
fail:
  s->cur_func = fd->parent;
  if (pfd) *pfd = NULL;
  return -1;
}

QJS_STATIC __exception int js_parse_function_decl(JSParseState *s,
                                                  JSParseFunctionEnum func_type,
                                                  JSFunctionKindEnum func_kind,
                                                  JSAtom func_name,
                                                  const uint8_t *ptr,
                                                  int function_line_num) {
  return js_parse_function_decl2_GC(s, func_type, func_kind, func_name, ptr,
                                    function_line_num, JS_PARSE_EXPORT_NONE,
                                    NULL);
}

static __exception int js_parse_program(JSParseState *s) {
  JSFunctionDef *fd = s->cur_func;
  int idx;
  fd->src_start = reinterpret_cast<const char *>(s->buf_ptr);

  if (next_token(s)) return -1;

  if (js_parse_directives(s)) return -1;

  fd->is_global_var = (fd->eval_type == LEPUS_EVAL_TYPE_GLOBAL) ||
                      (fd->eval_type == LEPUS_EVAL_TYPE_MODULE) ||
                      !(fd->js_mode & JS_MODE_STRICT);

  if (!s->is_module) {
    /* hidden variable for the return value */
    fd->eval_ret_idx = idx = add_var(s->ctx, fd, JS_ATOM__ret_);
    if (idx < 0) return -1;
  }

  while (s->token.val != TOK_EOF) {
    if (js_parse_source_element(s)) return -1;
  }

  if (!s->is_module) {
    /* return the value of the hidden variable eval_ret_idx  */
    emit_op(s, OP_get_loc);
    emit_u16(s, fd->eval_ret_idx);

    emit_op(s, OP_return);
  } else {
    emit_op(s, OP_return_undef);
  }

  get_caller_string(fd);
  return 0;
}

#endif

static LEPUSValue JS_EvalFunctionInternal(LEPUSContext *ctx, LEPUSValue fun_obj,
                                          LEPUSValueConst this_obj,
                                          JSVarRef **var_refs,
                                          LEPUSStackFrame *sf) {
  LEPUSValue ret_val;
  if (LEPUS_VALUE_IS_FUNCTION_BYTECODE(fun_obj)) {
    HandleScope func_scope{ctx, &fun_obj, HANDLE_TYPE_LEPUS_VALUE};
    fun_obj = js_closure_gc(ctx, fun_obj, var_refs, sf);
    ret_val = JS_CallFree_GC(ctx, fun_obj, this_obj, 0, NULL);
  } else if (LEPUS_VALUE_IS_MODULE(fun_obj)) {
#ifndef NO_QUICKJS_COMPILER
    LEPUSModuleDef *m;
    m = static_cast<LEPUSModuleDef *>(LEPUS_VALUE_GET_PTR(fun_obj));
    /* the module refcount should be >= 2 */
    if (js_link_module(ctx, m) < 0) goto fail;
    ret_val = js_evaluate_module(ctx, m);
    if (LEPUS_IsException(ret_val)) {
    fail:
      return LEPUS_EXCEPTION;
    }
#else
    return LEPUS_EXCEPTION;
#endif
  } else {
    ret_val = LEPUS_ThrowTypeError(ctx, "bytecode function expected");
  }
  return ret_val;
}

LEPUSValue JS_EvalFunction_GC(LEPUSContext *ctx, LEPUSValue fun_obj,
                              LEPUSValueConst this_obj) {
  return JS_EvalFunctionInternal(ctx, fun_obj, this_obj, NULL, NULL);
}

#ifndef NO_QUICKJS_COMPILER
/* 'input' must be zero terminated i.e. input[input_len] = '\0'. */
static LEPUSValue __JS_EvalInternal_GC(LEPUSContext *ctx,
                                       LEPUSValueConst this_obj,
                                       const char *input, size_t input_len,
                                       const char *filename, int flags,
                                       int scope_idx, bool debugger_eval,
                                       LEPUSStackFrame *debugger_frame,
                                       int start_line_number) {
  JSParseState s1, *s = &s1;
  int err, js_mode, eval_type;
  LEPUSValue fun_obj, ret_val;
  LEPUSStackFrame *sf;
  JSVarRef **var_refs;
  LEPUSFunctionBytecode *b;
  JSFunctionDef *fd;
  LEPUSModuleDef *m;
  LEPUSScriptSource *script = nullptr;

  js_parse_init(ctx, s, input, input_len, filename, start_line_number);
  HandleScope func_scope(ctx, &s->token, HANDLE_TYPE_LEPUS_TOKEN);
  skip_shebang(s);

  eval_type =
      debugger_eval ? LEPUS_EVAL_TYPE_DIRECT : (flags & LEPUS_EVAL_TYPE_MASK);
  m = NULL;
  if (eval_type == LEPUS_EVAL_TYPE_DIRECT) {
    LEPUSObject *p;
    sf = debugger_eval ? debugger_frame : ctx->rt->current_stack_frame;
    assert(sf != NULL);
    assert(LEPUS_VALUE_IS_OBJECT(sf->cur_func));
    p = LEPUS_VALUE_GET_OBJ(sf->cur_func);
    assert(lepus_class_has_bytecode(p->class_id));
    b = p->u.func.function_bytecode;
    var_refs = p->u.func.var_refs;
    js_mode = b->js_mode;
  } else {
    sf = NULL;
    b = NULL;
    var_refs = NULL;
    js_mode = 0;
    if (flags & LEPUS_EVAL_FLAG_STRICT) js_mode |= JS_MODE_STRICT;
    if (flags & LEPUS_EVAL_FLAG_STRIP) js_mode |= JS_MODE_STRIP;
    if (eval_type == LEPUS_EVAL_TYPE_MODULE) {
      JSAtom module_name = LEPUS_NewAtom(ctx, filename);
      if (module_name == JS_ATOM_NULL) return LEPUS_EXCEPTION;
      HandleScope func_scope(ctx->rt);
      func_scope.PushLEPUSAtom(module_name);
      m = js_new_module_def(ctx, module_name);
      if (!m) return LEPUS_EXCEPTION;
      func_scope.PushHandle(m, HANDLE_TYPE_DIR_HEAP_OBJ);
      js_mode |= JS_MODE_STRICT;
    }
  }
  fd = js_new_function_def_GC(ctx, NULL, TRUE, FALSE, filename, 1);
  func_scope.PushHandle(fd, HANDLE_TYPE_DIR_HEAP_OBJ);

#ifdef ENABLE_QUICKJS_DEBUGGER
  fd->column_num = 0;
#endif

  if (!fd) goto fail1;
  s->cur_func = fd;
  fd->eval_type = eval_type;
  fd->has_this_binding = (eval_type != LEPUS_EVAL_TYPE_DIRECT);
  if (eval_type == LEPUS_EVAL_TYPE_DIRECT) {
    fd->new_target_allowed = b->new_target_allowed;
    fd->super_call_allowed = b->super_call_allowed;
    fd->super_allowed = b->super_allowed;
    fd->arguments_allowed = b->arguments_allowed;
  } else {
    fd->new_target_allowed = FALSE;
    fd->super_call_allowed = FALSE;
    fd->super_allowed = FALSE;
    fd->arguments_allowed = TRUE;
  }
  fd->js_mode = js_mode;
  fd->func_name = JS_ATOM__eval_;
  if (b) {
    if (debugger_eval) {
      // use DEBUG_SCOPE_INDEX to add all lexical variables to debug eval
      // closure.
      int32_t idx = b->var_count ? DEBUG_SCOPE_INDEX : -1;
      if (add_closure_variables(ctx, fd, b, idx)) goto fail;
    } else {
      if (add_closure_variables(ctx, fd, b, scope_idx)) goto fail;
    }
  }
  fd->module = m;
  s->is_module = (m != NULL);
  s->allow_html_comments = !s->is_module;

  push_scope(s); /* body scope */
  fd->body_scope = fd->scope_level;

  err = js_parse_program(s);
#ifdef ENABLE_QUICKJS_DEBUGGER
  if (!debugger_eval && (ctx->debugger_parse_script || ctx->debugger_mode) &&
      !(flags & LEPUS_DEBUGGER_NO_PERSIST_SCRIPT)) {
    DebuggerParseScript(ctx, input, input_len, fd, filename, s->line_num, err,
                        start_line_number);
    script = fd->script;
  }
#endif

  if (err) {
  fail:
    goto fail1;
  }

  /* create the function object and all the enclosed functions */
  fun_obj = js_create_function(ctx, fd);

#ifdef ENABLE_QUICKJS_DEBUGGER
  if (script && ctx->debugger_mode) {
    // adjust breakpoint which pc is null(script_id = -1)
    AdjustBreakpoints(ctx, script);
  }
#endif

  if (LEPUS_IsException(fun_obj)) goto fail1;
  func_scope.PushHandle(&fun_obj, HANDLE_TYPE_LEPUS_VALUE);
  /* Could add a flag to avoid resolution if necessary */
  if (m) {
    m->func_obj = fun_obj;
    if (js_resolve_module(ctx, m) < 0) goto fail1;
    fun_obj = LEPUS_MKPTR(LEPUS_TAG_MODULE, m);
  }
  if (flags & LEPUS_EVAL_FLAG_COMPILE_ONLY) {
    ret_val = fun_obj;
  } else {
    ret_val = JS_EvalFunctionInternal(ctx, fun_obj, this_obj, var_refs, sf);
  }
  return ret_val;
fail1:
  /* XXX: should free all the unresolved dependencies */
  return LEPUS_EXCEPTION;
}

#endif

LEPUSValue JS_EvalObject(LEPUSContext *ctx, LEPUSValueConst this_obj,
                         LEPUSValueConst val, int flags, int scope_idx) {
#ifndef NO_QUICKJS_COMPILER
  LEPUSValue ret;
  const char *str;
  size_t len;

  if (!LEPUS_IsString(val)) return val;
  str = JS_ToCStringLen2_GC(ctx, &len, val, 0);
  if (!str) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, &str, HANDLE_TYPE_CSTRING);
  ret = JS_EvalInternal(ctx, this_obj, str, len, "<input>", flags, scope_idx);
  return ret;
#else
  return LEPUS_UNDEFINED;
#endif
}

LEPUSValue JS_Eval_GC(LEPUSContext *ctx, const char *input, size_t input_len,
                      const char *filename, int eval_flags,
                      int start_line_number) {
#ifndef NO_QUICKJS_COMPILER
  int eval_type = eval_flags & LEPUS_EVAL_TYPE_MASK;
  LEPUSValue ret;

  assert(eval_type == LEPUS_EVAL_TYPE_GLOBAL ||
         eval_type == LEPUS_EVAL_TYPE_MODULE);
  ret = JS_EvalInternal(ctx, ctx->global_obj, input, input_len, filename,
                        eval_flags, -1, false, NULL, start_line_number);
  return ret;
#else
  return LEPUS_UNDEFINED;
#endif
}

LEPUSValue JS_EvalBinary_GC(LEPUSContext *ctx, const uint8_t *buf,
                            size_t buf_len, int flags) {
  LEPUSValue obj;
  obj = LEPUS_ReadObject(ctx, buf, buf_len, LEPUS_READ_OBJ_BYTECODE);
  if (LEPUS_IsException(obj)) return obj;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  if (flags & LEPUS_EVAL_BINARY_LOAD_ONLY) {
    return obj;
  } else {
    /* if a module, we load the dependencies here */

#ifndef NO_QUICKJS_COMPILER
    if (LEPUS_VALUE_IS_MODULE(obj)) {
      LEPUSModuleDef *m =
          static_cast<LEPUSModuleDef *>(LEPUS_VALUE_GET_PTR(obj));
      if (js_resolve_module(ctx, m) < 0) {
        return LEPUS_EXCEPTION;
      }
    }
#endif

    return JS_EvalFunction_GC(ctx, obj, ctx->global_obj);
  }
}

/*******************************************************************/
/* binary object writer & reader */

#define BC_BASE_VERSION 1
#define BC_BE_VERSION 0x40
#ifdef WORDS_BIGENDIAN
#define BC_VERSION (BC_BASE_VERSION | BC_BE_VERSION)
#else
#define BC_VERSION BC_BASE_VERSION
#endif

#ifndef NO_QUICKJS_COMPILER
#define MAGIC_SET (1 << 0)
#define MAGIC_WEAK (1 << 1)

#endif

typedef struct BCReaderState {
  LEPUSContext *ctx;
  const uint8_t *buf_start, *ptr, *buf_end;
  uint32_t first_atom;
  uint32_t idx_to_atom_count;
  JSAtom *idx_to_atom;
  int error_state;
  BOOL allow_bytecode;
  BOOL is_rom_data;
#ifdef DUMP_READ_OBJECT
  const uint8_t *ptr_last;
  int level;
#endif
} BCReaderState;

#ifdef DUMP_READ_OBJECT
#else
#define bc_read_trace(...)
#endif

static int bc_read_error_end(BCReaderState *s) {
  if (!s->error_state) {
    LEPUS_ThrowSyntaxError(s->ctx, "read after the end of the buffer");
  }
  return s->error_state = -1;
}

static int bc_get_u8(BCReaderState *s, uint8_t *pval) {
  if (unlikely(s->buf_end - s->ptr < 1)) {
    *pval = 0; /* avoid warning */
    return bc_read_error_end(s);
  }
  *pval = *s->ptr++;
  return 0;
}

static __attribute__((unused)) int bc_get_u32(BCReaderState *s,
                                              uint32_t *pval) {
  if (unlikely(s->buf_end - s->ptr < 4)) {
    *pval = 0; /* avoid warning */
    return bc_read_error_end(s);
  }
  *pval = get_u32(s->ptr);
  s->ptr += 4;
  return 0;
}

#ifdef ENABLE_BUILTIN_SERIALIZE
static LEPUSValue js_map_constructor(LEPUSContext *ctx,
                                     LEPUSValueConst new_target, int argc,
                                     LEPUSValueConst *argv, int magic);

LEPUSValue JS_ReadError_GC(BCReaderState *s) {
  LEPUSContext *ctx = s->ctx;
  LEPUSValue obj = LEPUS_UNDEFINED, msg = LEPUS_UNDEFINED,
             proto = LEPUS_UNDEFINED, stack = LEPUS_UNDEFINED;
  int8_t magic;
  HandleScope func_scope(ctx);
  LEPUSValue val = LEPUS_UNDEFINED;
  if (bc_get_u8(s, reinterpret_cast<uint8_t *>(&magic))) goto fail;
  val = JS_ReadObjectRec(s);
  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  msg = JS_ToStringFree(ctx, val);
  if (LEPUS_IsException(msg)) goto fail;
  func_scope.PushHandle(&msg, HANDLE_TYPE_LEPUS_VALUE);
  val = JS_ReadObjectRec(s);
  stack = JS_ToStringFree(ctx, val);
  if (LEPUS_IsException(stack)) goto fail;
  func_scope.PushHandle(&stack, HANDLE_TYPE_LEPUS_VALUE);
  if (magic < 0) {
    proto = ctx->class_proto[JS_CLASS_ERROR];
  } else {
    proto = ctx->native_error_proto[magic];
  }
  func_scope.PushHandle(&proto, HANDLE_TYPE_LEPUS_VALUE);
  obj = JS_NewObjectProtoClass_GC(ctx, proto, JS_CLASS_ERROR);
  if (LEPUS_IsException(obj)) goto fail;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_message, msg,
                            LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE);
  JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_stack, stack,
                            LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE);
  if (magic == JS_AGGREGATE_ERROR) {
    LEPUSValue error_list = JS_ReadObjectRec(s);
    if (LEPUS_IsException(error_list)) {
      goto fail;
    }
    HandleScope block_scope(ctx, &error_list, HANDLE_TYPE_LEPUS_VALUE);
    JSAtom atom_errors = LEPUS_NewAtom(ctx, "errors");
    block_scope.PushLEPUSAtom(atom_errors);
    JS_DefinePropertyValue_GC(ctx, obj, atom_errors, error_list,
                              LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE);
  }
  return obj;
fail:
  return LEPUS_EXCEPTION;
}

LEPUSValue JS_ReadRegExp_GC(BCReaderState *s) {
  LEPUSContext *ctx = s->ctx;
  LEPUSValue obj, pattern, bc;
  pattern = JS_ReadObjectRec(s);
  HandleScope func_scope(ctx, &pattern, HANDLE_TYPE_LEPUS_VALUE);
  bc = JS_ReadObjectRec(s);
  func_scope.PushHandle(&bc, HANDLE_TYPE_LEPUS_VALUE);
  obj = js_regexp_constructor_internal_gc(ctx, LEPUS_UNDEFINED, pattern, bc);
  return obj;
}

LEPUSValue JS_ReadMap_GC(BCReaderState *s) {
  LEPUSContext *ctx = s->ctx;
  uint8_t magic;
  bc_get_u8(s, &magic);
  const bool is_set = magic & MAGIC_SET;
  LEPUSValue obj = js_map_constructor(ctx, LEPUS_UNDEFINED, 0, NULL, magic);
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  uint32_t count;
  bc_get_u32(s, &count);
  LEPUSValue adder = JS_GetPropertyInternal_GC(
      ctx, obj, is_set ? JS_ATOM_add : JS_ATOM_set, obj, 0);
  if (unlikely(LEPUS_IsException(adder))) goto fail;
  if (unlikely(!LEPUS_IsFunction(ctx, adder))) {
    LEPUS_ThrowTypeError(ctx, "set/add is not a function");
    goto fail;
  }
  func_scope.PushHandle(&adder, HANDLE_TYPE_LEPUS_VALUE);
  for (uint32_t cnt = 0; cnt < count; ++cnt) {
    LEPUSValue key = JS_ReadObjectRec(s);
    LEPUSValue value = LEPUS_UNDEFINED;
    HandleScope block_scope(ctx, &key, HANDLE_TYPE_LEPUS_VALUE);
    block_scope.PushHandle(&value, HANDLE_TYPE_LEPUS_VALUE);
    LEPUSValue ret;
    if (!is_set) {
      LEPUSValue args[2];
      args[0] = key;
      value = JS_ReadObjectRec(s);
      args[1] = value;

      ret = JS_Call_GC(ctx, adder, obj, 2, args);
    } else {
      ret = JS_Call_GC(ctx, adder, obj, 1, &key);
    }
    if (unlikely(LEPUS_IsException(ret))) goto fail;
  }
  return obj;
fail:
  return LEPUS_EXCEPTION;
}

#endif  // ENABLE_BUILTIN_SERIALIZE

/*******************************************************************/
/* runtime functions & objects */

static LEPUSValue js_string_constructor(LEPUSContext *ctx,
                                        LEPUSValueConst this_val, int argc,
                                        LEPUSValueConst *argv);
static LEPUSValue js_boolean_constructor(LEPUSContext *ctx,
                                         LEPUSValueConst this_val, int argc,
                                         LEPUSValueConst *argv);
static LEPUSValue js_number_constructor(LEPUSContext *ctx,
                                        LEPUSValueConst this_val, int argc,
                                        LEPUSValueConst *argv);

static int check_function(LEPUSContext *ctx, LEPUSValueConst obj) {
  if (likely(LEPUS_IsFunction(ctx, obj))) return 0;
  LEPUS_ThrowTypeError(ctx, "not a function");
  return -1;
}

static int check_exception_free(LEPUSContext *ctx, LEPUSValue obj) {
  return LEPUS_IsException(obj);
}

static JSAtom find_atom(LEPUSContext *ctx, const char *name) {
  JSAtom atom;
  int len;

  if (*name == '[') {
    name++;
    len = strlen(name) - 1;
    /* We assume 8 bit non null strings, which is the case for these
       symbols */
    for (atom = JS_ATOM_Symbol_toPrimitive; atom < JS_ATOM_END; atom++) {
      JSAtomStruct *p = ctx->rt->atom_array[atom];
      JSString *str = p;
      if (str->len == len && !memcmp(str->u.str8, name, len)) return atom;
    }
    abort();
  } else {
    atom = LEPUS_NewAtom(ctx, name);
  }
  return atom;
}

static LEPUSValue JS_InstantiateFunctionListItem2(LEPUSContext *ctx,
                                                  LEPUSObject *p, JSAtom atom,
                                                  void *opaque) {
  const LEPUSCFunctionListEntry *e =
      static_cast<LEPUSCFunctionListEntry *>(opaque);
  LEPUSValue val = LEPUS_UNDEFINED, proto = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);

  switch (e->def_type) {
    case LEPUS_DEF_CFUNC:
      val = JS_NewCFunction2_GC(
          ctx, e->u.func.cfunc.generic, e->name, e->u.func.length,
          static_cast<LEPUSCFunctionEnum>(e->u.func.cproto), e->magic);
      break;
    case LEPUS_DEF_PROP_STRING:
      val = JS_NewAtomString_GC(ctx, e->u.str);
      break;
    case LEPUS_DEF_OBJECT:
      if (atom == JS_ATOM_Symbol_unscopables) {
        proto = LEPUS_NULL;
      } else {
        proto = ctx->class_proto[JS_CLASS_OBJECT];
      }
      val = JS_NewObjectProtoList(ctx, proto, e->u.prop_list.tab,
                                  e->u.prop_list.len);
      break;
    default:
      abort();
  }
  return val;
}

static int JS_InstantiateFunctionListItem(LEPUSContext *ctx,
                                          LEPUSValueConst obj, JSAtom atom,
                                          const LEPUSCFunctionListEntry *e,
                                          bool need_find_prop = true) {
  LEPUSValue val = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  int prop_flags = e->prop_flags;

  switch (e->def_type) {
    case LEPUS_DEF_ALIAS: {
      JSAtom atom1 = find_atom(ctx, e->u.alias.name);
      HandleScope block_scope(ctx);
      block_scope.PushLEPUSAtom(atom1);
      switch (e->u.alias.base) {
        case -1:
          val = JS_GetPropertyInternal_GC(ctx, obj, atom1, obj, 0);
          break;
        case 0:
          val = JS_GetPropertyInternal_GC(ctx, ctx->global_obj, atom1,
                                          ctx->global_obj, 0);
          break;
        case 1:
          val = JS_GetPropertyInternal_GC(ctx, ctx->class_proto[JS_CLASS_ARRAY],
                                          atom1,
                                          ctx->class_proto[JS_CLASS_ARRAY], 0);
          break;
        default:
          abort();
      }
      if (atom == JS_ATOM_Symbol_toPrimitive) {
        /* Symbol.toPrimitive functions are not writable */
        prop_flags = LEPUS_PROP_CONFIGURABLE;
      } else if (atom == JS_ATOM_Symbol_hasInstance) {
        /* Function.prototype[Symbol.hasInstance] is not writable nor
         * configurable */
        prop_flags = 0;
      }
    } break;
    case LEPUS_DEF_CFUNC:
      if (atom == JS_ATOM_Symbol_toPrimitive) {
        /* Symbol.toPrimitive functions are not writable */
        prop_flags = LEPUS_PROP_CONFIGURABLE;
      } else if (atom == JS_ATOM_Symbol_hasInstance) {
        /* Function.prototype[Symbol.hasInstance] is not writable nor
         * configurable */
        prop_flags = 0;
      }
      JS_DefineAutoInitProperty_GC(
          ctx, obj, atom, JS_InstantiateFunctionListItem2,
          reinterpret_cast<void *>(const_cast<LEPUSCFunctionListEntry *>(e)),
          prop_flags, need_find_prop);
      return 0;
    case LEPUS_DEF_CGETSET:
    case LEPUS_DEF_CGETSET_MAGIC: {
      HandleScope block_scope(ctx->rt);
      LEPUSValue getter, setter;
      char buf[64];

      getter = LEPUS_UNDEFINED;
      block_scope.PushHandle(&getter, HANDLE_TYPE_LEPUS_VALUE);
      if (e->u.getset.get.generic) {
        snprintf(buf, sizeof(buf), "get %s", e->name);
        getter = JS_NewCFunction2_GC(ctx, e->u.getset.get.generic, buf, 0,
                                     e->def_type == LEPUS_DEF_CGETSET_MAGIC
                                         ? LEPUS_CFUNC_getter_magic
                                         : LEPUS_CFUNC_getter,
                                     e->magic);
      }
      setter = LEPUS_UNDEFINED;
      block_scope.PushHandle(&setter, HANDLE_TYPE_LEPUS_VALUE);
      if (e->u.getset.set.generic) {
        snprintf(buf, sizeof(buf), "set %s", e->name);
        setter = JS_NewCFunction2_GC(ctx, e->u.getset.set.generic, buf, 1,
                                     e->def_type == LEPUS_DEF_CGETSET_MAGIC
                                         ? LEPUS_CFUNC_setter_magic
                                         : LEPUS_CFUNC_setter,
                                     e->magic);
      }
      JS_DefinePropertyGetSet_GC(ctx, obj, atom, getter, setter, prop_flags);
      return 0;
    } break;
    case LEPUS_DEF_PROP_INT32:
      val = LEPUS_NewInt32(ctx, e->u.i32);
      break;
    case LEPUS_DEF_PROP_INT64:
      val = JS_NewInt64_GC(ctx, e->u.i64);
      break;
    case LEPUS_DEF_PROP_DOUBLE:
      val = __JS_NewFloat64(ctx, e->u.f64);
      break;
    case LEPUS_DEF_PROP_UNDEFINED:
      val = LEPUS_UNDEFINED;
      break;
    case LEPUS_DEF_PROP_STRING:
      JS_DefineAutoInitProperty_GC(
          ctx, obj, atom, JS_InstantiateFunctionListItem2,
          reinterpret_cast<void *>(const_cast<LEPUSCFunctionListEntry *>(e)),
          prop_flags, need_find_prop);
      return 0;
    case LEPUS_DEF_OBJECT:
      JS_DefineAutoInitProperty_GC(
          ctx, obj, atom, JS_InstantiateFunctionListItem2,
          reinterpret_cast<void *>(const_cast<LEPUSCFunctionListEntry *>(e)),
          prop_flags, need_find_prop);
      return 0;
    case LEPUS_DEF_PROP_ATOM:
      val = JS_AtomToValue_GC(ctx, e->u.i32);
      break;
    case LEPUS_DEF_PROP_BOOL:
      val = LEPUS_NewBool(ctx, e->u.i32);
      break;
    default:
      abort();
  }
  JS_DefinePropertyValue_GC(ctx, obj, atom, val, prop_flags);
  return 0;
}

void JS_SetPropertyFunctionList_GC(LEPUSContext *ctx, LEPUSValueConst obj,
                                   const LEPUSCFunctionListEntry *tab,
                                   int len) {
  int i, prop_flags;
  HandleScope func_scope(ctx->rt);

  for (i = 0; i < len; i++) {
    const LEPUSCFunctionListEntry *e = &tab[i];
    JSAtom atom = find_atom(ctx, e->name);
    func_scope.PushLEPUSAtom(atom);
    JS_InstantiateFunctionListItem(ctx, obj, atom, e);
  }
}

/* Note: 'func_obj' is not necessarily a constructor */
static void JS_SetConstructor2(LEPUSContext *ctx, LEPUSValueConst func_obj,
                               LEPUSValueConst proto, int proto_flags,
                               int ctor_flags) {
  JS_DefinePropertyValue_GC(ctx, func_obj, JS_ATOM_prototype, proto,
                            proto_flags);
  JS_DefinePropertyValue_GC(ctx, proto, JS_ATOM_constructor, func_obj,
                            ctor_flags);
  set_cycle_flag(ctx, func_obj);
  set_cycle_flag(ctx, proto);
}

static void JS_SetFunctionListForConstructor(LEPUSContext *ctx,
                                             LEPUSValueConst obj,
                                             const LEPUSCFunctionListEntry *tab,
                                             int len) {
  int i, prop_flags;

  for (i = 0; i < len; i++) {
    const LEPUSCFunctionListEntry *e = &tab[i];
    JSAtom atom = find_atom(ctx, e->name);
    HandleScope block_scope(ctx);
    block_scope.PushLEPUSAtom(atom);
    JS_InstantiateFunctionListItem(ctx, obj, atom, e, false);
  }
}

static LEPUSValue JS_NewCConstructor(
    LEPUSContext *ctx, int32_t class_id, const char *name, LEPUSCFunction *func,
    int32_t length, LEPUSCFunctionEnum cproto, int32_t magic,
    LEPUSValueConst parent_ctor, const LEPUSCFunctionListEntry *ctor_fields,
    int32_t n_ctor_fields, const LEPUSCFunctionListEntry *proto_fields,
    int32_t n_proto_fields, int32_t flags) {
  LEPUSValue ctor, proto, parent_proto;
  int32_t proto_class_id, proto_flags, ctor_flags;
  ctor = proto = parent_proto = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &proto, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&ctor, HANDLE_TYPE_LEPUS_VALUE);
  proto_flags = 0;

  if (flags & JS_NEW_CTOR_READONLY) {
    ctor_flags = LEPUS_PROP_CONFIGURABLE;
  } else {
    ctor_flags = LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE;
  }

  if (LEPUS_IsUndefined(parent_ctor)) {
    parent_proto = ctx->class_proto[JS_CLASS_OBJECT];
    parent_ctor = ctx->function_proto;
  } else {
    parent_proto = LEPUS_GetProperty(ctx, parent_ctor, JS_ATOM_prototype);
    if (LEPUS_IsException(parent_proto)) return LEPUS_EXCEPTION;
  }

  if (flags & JS_NEW_CTOR_PROTO_EXIST) {
    proto = ctx->class_proto[class_id];
  } else {
    if (flags & JS_NEW_CTOR_PROTO_CLASS) {
      proto_class_id = class_id;
    } else {
      proto_class_id = JS_CLASS_OBJECT;
    }
    proto = JS_NewObjectProtoClassAlloc(ctx, parent_proto, proto_class_id,
                                        n_proto_fields + 1);
    if (LEPUS_IsException(proto)) goto fail;

    if (class_id >= 0) ctx->class_proto[class_id] = proto;
  }
  JS_SetFunctionListForConstructor(ctx, proto, proto_fields, n_proto_fields);

  // addtional fields: name, length, prototype
  ctor = JS_NewCFunction3(ctx, func, name, length, cproto, magic, parent_ctor,
                          n_ctor_fields + 3);

  if (LEPUS_IsException(ctor)) goto fail;
  JS_SetFunctionListForConstructor(ctx, ctor, ctor_fields, n_ctor_fields);
  if (!(flags & JS_NEW_CTOR_NO_GLOBAL)) {
    if (JS_DefinePropertyValueStr_GC(
            ctx, ctx->global_obj, name, ctor,
            LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE) < 0)
      goto fail;
  }
  JS_SetConstructor2(ctx, ctor, proto, proto_flags, ctor_flags);

  return ctor;
fail:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_global_eval(LEPUSContext *ctx, LEPUSValueConst this_val,
                                 int argc, LEPUSValueConst *argv) {
  return JS_EvalObject(ctx, ctx->global_obj, argv[0], LEPUS_EVAL_TYPE_INDIRECT,
                       -1);
}

static LEPUSValue js_global_isNaN(LEPUSContext *ctx, LEPUSValueConst this_val,
                                  int argc, LEPUSValueConst *argv) {
  double d;

  /* XXX: does this work for bigfloat? */
  if (unlikely(JS_ToFloat64_GC(ctx, &d, argv[0]))) return LEPUS_EXCEPTION;
  return LEPUS_NewBool(ctx, isnan(d));
}

static LEPUSValue js_global_isFinite(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv) {
  double d;
  if (unlikely(JS_ToFloat64_GC(ctx, &d, argv[0]))) return LEPUS_EXCEPTION;
  return LEPUS_NewBool(ctx, isfinite(d));
}

/* Object class */

LEPUSValue JS_ToObject_GC(LEPUSContext *ctx, LEPUSValueConst val) {
  int64_t tag = LEPUS_VALUE_GET_NORM_TAG(val);
  LEPUSValue obj = LEPUS_UNDEFINED;
  switch (tag) {
    default:
    case LEPUS_TAG_NULL:
    case LEPUS_TAG_UNDEFINED:
      return LEPUS_ThrowTypeError(ctx, "cannot convert to object");
    case LEPUS_TAG_OBJECT:
    case LEPUS_TAG_EXCEPTION:
      return val;
    case LEPUS_TAG_BIG_INT:
      obj = JS_NewObjectClass_GC(ctx, JS_CLASS_BIG_INT);
      goto set_value;
    case LEPUS_TAG_INT:
    case LEPUS_TAG_FLOAT64:
      obj = JS_NewObjectClass_GC(ctx, JS_CLASS_NUMBER);
      goto set_value;
    case LEPUS_TAG_STRING:
      /* XXX: should call the string constructor */
      {
        JSString *p1 = LEPUS_VALUE_GET_STRING(val);
        obj = JS_NewObjectClass_GC(ctx, JS_CLASS_STRING);
        HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
        JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_length,
                                  LEPUS_NewInt32(ctx, p1->len), 0);
      }
      goto set_value;
    case LEPUS_TAG_SEPARABLE_STRING: {
      obj = JS_NewObjectClass_GC(ctx, JS_CLASS_STRING);
      HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
      auto *separable_string = JS_GetSeparableString(val);
      JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_length,
                                LEPUS_NewInt32(ctx, separable_string->len), 0);
    }
      goto set_value;
    case LEPUS_TAG_BOOL:
      obj = JS_NewObjectClass_GC(ctx, JS_CLASS_BOOLEAN);
      goto set_value;
    case LEPUS_TAG_SYMBOL:
      obj = JS_NewObjectClass_GC(ctx, JS_CLASS_SYMBOL);
    set_value:
      if (!LEPUS_IsException(obj)) JS_SetObjectData(ctx, obj, val);
      return obj;

// Primjs begin
#ifdef ENABLE_LEPUSNG
    case LEPUS_TAG_LEPUS_REF: {
      auto *js_ref = static_cast<LEPUSLepusRef *>(LEPUS_VALUE_GET_PTR(val));
      auto &obj = js_ref->lepus_val;
      if (LEPUS_VALUE_IS_OBJECT(obj)) return obj;
      return obj = ctx->rt->js_callbacks_.convert_to_object(ctx, val);
    }
#endif
      // Primjs end
  }
}

static LEPUSValue JS_ToObjectFree(LEPUSContext *ctx, LEPUSValue val) {
  LEPUSValue obj = JS_ToObject_GC(ctx, val);
  return obj;
}

static LEPUSValue JS_ToObject_expect_lepusref(LEPUSContext *ctx,
                                              LEPUSValue val) {
#ifdef ENABLE_LEPUSNG
  if (LEPUS_IsLepusRef(val)) {
    return val;
  }
#endif
  return JS_ToObject_GC(ctx, val);
}

static int js_obj_to_desc(LEPUSContext *ctx, LEPUSPropertyDescriptor *d,
                          LEPUSValueConst desc) {
  HandleScope func_scope(ctx);
  LEPUSValue val, getter, setter;
  int flags;

  if (!LEPUS_IsObject(desc)) {
    JS_ThrowTypeErrorNotAnObject(ctx);
    return -1;
  }
  flags = 0;
  val = LEPUS_UNDEFINED;
  getter = LEPUS_UNDEFINED;
  setter = LEPUS_UNDEFINED;
  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&getter, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&setter, HANDLE_TYPE_LEPUS_VALUE);
  if (JS_HasProperty_GC(ctx, desc, JS_ATOM_configurable)) {
    LEPUSValue prop =
        JS_GetPropertyInternal_GC(ctx, desc, JS_ATOM_configurable, desc, 0);
    if (LEPUS_IsException(prop)) goto fail;
    flags |= LEPUS_PROP_HAS_CONFIGURABLE;
    if (JS_ToBoolFree_GC(ctx, prop)) flags |= LEPUS_PROP_CONFIGURABLE;
  }
  if (JS_HasProperty_GC(ctx, desc, JS_ATOM_writable)) {
    LEPUSValue prop =
        JS_GetPropertyInternal_GC(ctx, desc, JS_ATOM_writable, desc, 0);
    if (LEPUS_IsException(prop)) goto fail;
    flags |= LEPUS_PROP_HAS_WRITABLE;
    if (JS_ToBoolFree_GC(ctx, prop)) flags |= LEPUS_PROP_WRITABLE;
  }
  if (JS_HasProperty_GC(ctx, desc, JS_ATOM_enumerable)) {
    LEPUSValue prop =
        JS_GetPropertyInternal_GC(ctx, desc, JS_ATOM_enumerable, desc, 0);
    if (LEPUS_IsException(prop)) goto fail;
    flags |= LEPUS_PROP_HAS_ENUMERABLE;
    if (JS_ToBoolFree_GC(ctx, prop)) flags |= LEPUS_PROP_ENUMERABLE;
  }
  if (JS_HasProperty_GC(ctx, desc, JS_ATOM_value)) {
    flags |= LEPUS_PROP_HAS_VALUE;
    val = JS_GetPropertyInternal_GC(ctx, desc, JS_ATOM_value, desc, 0);
    if (LEPUS_IsException(val)) goto fail;
  }
  if (JS_HasProperty_GC(ctx, desc, JS_ATOM_get)) {
    flags |= LEPUS_PROP_HAS_GET;
    getter = JS_GetPropertyInternal_GC(ctx, desc, JS_ATOM_get, desc, 0);
    if (LEPUS_IsException(getter) ||
        !(LEPUS_IsUndefined(getter) || LEPUS_IsFunction(ctx, getter))) {
      LEPUS_ThrowTypeError(ctx, "invalid getter");
      goto fail;
    }
  }
  if (JS_HasProperty_GC(ctx, desc, JS_ATOM_set)) {
    flags |= LEPUS_PROP_HAS_SET;
    setter = JS_GetPropertyInternal_GC(ctx, desc, JS_ATOM_set, desc, 0);
    if (LEPUS_IsException(setter) ||
        !(LEPUS_IsUndefined(setter) || LEPUS_IsFunction(ctx, setter))) {
      LEPUS_ThrowTypeError(ctx, "invalid setter");
      goto fail;
    }
  }
  if ((flags & (LEPUS_PROP_HAS_SET | LEPUS_PROP_HAS_GET)) &&
      (flags & (LEPUS_PROP_HAS_VALUE | LEPUS_PROP_HAS_WRITABLE))) {
    LEPUS_ThrowTypeError(ctx,
                         "cannot have setter/getter and value or writable");
    goto fail;
  }
  d->flags = flags;
  d->value = val;
  d->getter = getter;
  d->setter = setter;
  return 0;
fail:
  return -1;
}

static __exception int JS_DefinePropertyDesc(LEPUSContext *ctx,
                                             LEPUSValueConst obj, JSAtom prop,
                                             LEPUSValueConst desc, int flags) {
  HandleScope func_scope(ctx);
  LEPUSPropertyDescriptor d;
  func_scope.PushLEPUSPropertyDescriptor(&d);
  int ret;

  if (js_obj_to_desc(ctx, &d, desc) < 0) return -1;

  ret = JS_DefineProperty_GC(ctx, obj, prop, d.value, d.getter, d.setter,
                             d.flags | flags);
  return ret;
}

static __exception int JS_ObjectDefineProperties(LEPUSContext *ctx,
                                                 LEPUSValueConst obj,
                                                 LEPUSValueConst properties) {
  LEPUSValue props, desc;
  LEPUSObject *p;
  LEPUSPropertyEnum *atoms = nullptr;
  HandleScope func_scope(ctx, &atoms, HANDLE_TYPE_HEAP_OBJ);
  uint32_t len, i;
  int ret = -1;

  if (!LEPUS_IsObject(obj)) {
    JS_ThrowTypeErrorNotAnObject(ctx);
    return -1;
  }
  desc = LEPUS_UNDEFINED;
  func_scope.PushHandle(&desc, HANDLE_TYPE_LEPUS_VALUE);
  props = JS_ToObject_GC(ctx, properties);
  if (LEPUS_IsException(props)) return -1;
  func_scope.PushHandle(&props, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_OBJ(props);
  if (JS_GetOwnPropertyNamesInternal(
          ctx, &atoms, &len, p,
          LEPUS_GPN_ENUM_ONLY | LEPUS_GPN_STRING_MASK | LEPUS_GPN_SYMBOL_MASK) <
      0)
    goto exception;
  for (i = 0; i < len; i++) {
    desc = JS_GetPropertyInternal_GC(ctx, props, atoms[i].atom, props, 0);
    if (LEPUS_IsException(desc)) goto exception;
    if (JS_DefinePropertyDesc(ctx, obj, atoms[i].atom, desc, LEPUS_PROP_THROW) <
        0)
      goto exception;
  }
  ret = 0;

exception:
  return ret;
}

static LEPUSValue js_object_constructor(LEPUSContext *ctx,
                                        LEPUSValueConst new_target, int argc,
                                        LEPUSValueConst *argv) {
  LEPUSValue ret;
  if (!LEPUS_IsUndefined(new_target) &&
      LEPUS_VALUE_GET_OBJ(new_target) !=
          LEPUS_VALUE_GET_OBJ(JS_GetActiveFunction(ctx))) {
    ret = js_create_from_ctor_GC(ctx, new_target, JS_CLASS_OBJECT);
  } else {
    int64_t tag = LEPUS_VALUE_GET_NORM_TAG(argv[0]);
    switch (tag) {
      case LEPUS_TAG_NULL:
      case LEPUS_TAG_UNDEFINED:
        ret = JS_NewObject_GC(ctx);
        break;
      default:
        ret = JS_ToObject_GC(ctx, argv[0]);
        break;
    }
  }
  return ret;
}

static LEPUSValue js_object_create(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
  LEPUSValueConst proto = LEPUS_UNDEFINED, props = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &proto, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&props, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue obj;

  proto = argv[0];
  if (!LEPUS_IsObject(proto) && !LEPUS_IsNull(proto))
    return LEPUS_ThrowTypeError(ctx, "not a prototype");
  obj = JS_NewObjectProto_GC(ctx, proto);
  if (LEPUS_IsException(obj)) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  props = argv[1];
  if (!LEPUS_IsUndefined(props)) {
    if (JS_ObjectDefineProperties(ctx, obj, props)) {
      return LEPUS_EXCEPTION;
    }
  }
  return obj;
}

static LEPUSValue js_object_getPrototypeOf(LEPUSContext *ctx,
                                           LEPUSValueConst this_val, int argc,
                                           LEPUSValueConst *argv, int magic) {
  LEPUSValueConst val;

  val = argv[0];
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_VALUE_IS_NOT_OBJECT(val)) {
    /* ES6 feature non compatible with ES5.1: primitive types are
       accepted */
    if (magic || LEPUS_VALUE_IS_NULL(val) || LEPUS_VALUE_IS_UNDEFINED(val))
      return JS_ThrowTypeErrorNotAnObject(ctx);
  }
  return JS_GetPrototype_GC(ctx, val);
}

static LEPUSValue js_object_setPrototypeOf(LEPUSContext *ctx,
                                           LEPUSValueConst this_val, int argc,
                                           LEPUSValueConst *argv) {
  LEPUSValueConst obj;
  obj = argv[0];
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  if (JS_SetPrototypeInternal_GC(ctx, obj, argv[1], TRUE) < 0)
    return LEPUS_EXCEPTION;
  return obj;
}

/* magic = 1 if called as Reflect.defineProperty */
static LEPUSValue js_object_defineProperty(LEPUSContext *ctx,
                                           LEPUSValueConst this_val, int argc,
                                           LEPUSValueConst *argv, int magic) {
  LEPUSValueConst obj, prop, desc;
  int ret, flags;
  JSAtom atom;

  obj = JSRef2Value(ctx, argv[0]);
  prop = argv[1];
  desc = argv[2];
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&prop, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&desc, HANDLE_TYPE_LEPUS_VALUE);

  if (LEPUS_VALUE_IS_NOT_OBJECT(obj)) return JS_ThrowTypeErrorNotAnObject(ctx);
  atom = js_value_to_atom_gc(ctx, prop);
  if (unlikely(atom == JS_ATOM_NULL)) return LEPUS_EXCEPTION;
  func_scope.PushLEPUSAtom(atom);
  flags = 0;
  if (!magic) flags |= LEPUS_PROP_THROW;
  ret = JS_DefinePropertyDesc(ctx, obj, atom, desc, flags);
  if (ret < 0) {
    return LEPUS_EXCEPTION;
  } else if (magic) {
    return LEPUS_NewBool(ctx, ret);
  } else {
    return obj;
  }
}

static LEPUSValue js_object_defineProperties(LEPUSContext *ctx,
                                             LEPUSValueConst this_val, int argc,
                                             LEPUSValueConst *argv) {
  // defineProperties(obj, properties)
  LEPUSValueConst obj = JSRef2Value(ctx, argv[0]);
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);

  if (JS_ObjectDefineProperties(ctx, obj, argv[1]))
    return LEPUS_EXCEPTION;
  else
    return obj;
}

/* magic = 1 if called as __defineSetter__ */
static LEPUSValue js_object___defineGetter__(LEPUSContext *ctx,
                                             LEPUSValueConst this_val, int argc,
                                             LEPUSValueConst *argv, int magic) {
  LEPUSValue obj;
  LEPUSValueConst prop, value, get, set;
  int ret, flags;
  JSAtom atom;

  prop = argv[0];
  value = argv[1];
  HandleScope func_scope(ctx, &prop, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&value, HANDLE_TYPE_LEPUS_VALUE);

  obj = JS_ToObject_GC(ctx, this_val);
  if (LEPUS_IsException(obj)) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);

  if (check_function(ctx, value)) {
    return LEPUS_EXCEPTION;
  }
  atom = js_value_to_atom_gc(ctx, prop);
  if (unlikely(atom == JS_ATOM_NULL)) {
    return LEPUS_EXCEPTION;
  }
  func_scope.PushLEPUSAtom(atom);
  flags = LEPUS_PROP_THROW | LEPUS_PROP_HAS_ENUMERABLE | LEPUS_PROP_ENUMERABLE |
          LEPUS_PROP_HAS_CONFIGURABLE | LEPUS_PROP_CONFIGURABLE;
  if (magic) {
    get = LEPUS_UNDEFINED;
    set = value;
    flags |= LEPUS_PROP_HAS_SET;
  } else {
    get = value;
    set = LEPUS_UNDEFINED;
    flags |= LEPUS_PROP_HAS_GET;
  }
  func_scope.PushHandle(&get, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&set, HANDLE_TYPE_LEPUS_VALUE);
  ret = JS_DefineProperty_GC(ctx, obj, atom, LEPUS_UNDEFINED, get, set, flags);
  if (ret < 0) {
    return LEPUS_EXCEPTION;
  } else {
    return LEPUS_UNDEFINED;
  }
}

LEPUSValue js_object_getOwnPropertyDescriptor_GC(LEPUSContext *ctx,
                                                 LEPUSValueConst this_val,
                                                 int argc,
                                                 LEPUSValueConst *argv,
                                                 int magic) {
  HandleScope func_scope(ctx);
  LEPUSValueConst obj, prop;
  JSAtom atom;
  LEPUSValue ret;
  LEPUSPropertyDescriptor desc;
  func_scope.PushLEPUSPropertyDescriptor(&desc);
  int res, flags;
  // <Primjs begin>
  obj = JSRef2Value(ctx, argv[0]);
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  // <Primjs end>
  if (LEPUS_VALUE_IS_NOT_OBJECT(obj)) {
    /* ES6 feature non compatible with ES5.1: obj can be a primitive type */
    if (magic || LEPUS_VALUE_IS_NULL(obj) || LEPUS_VALUE_IS_UNDEFINED(obj))
      return JS_ThrowTypeErrorNotAnObject(ctx);
  }
  prop = argv[1];
  atom = js_value_to_atom_gc(ctx, prop);
  if (unlikely(atom == JS_ATOM_NULL)) return LEPUS_EXCEPTION;
  func_scope.PushLEPUSAtom(atom);
  ret = LEPUS_UNDEFINED;
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_VALUE_IS_OBJECT(obj)) {
    res = JS_GetOwnPropertyInternal(ctx, &desc, LEPUS_VALUE_GET_OBJ(obj), atom);
    if (res < 0) goto exception;
    if (res) {
      ret = JS_NewObject_GC(ctx);
      if (LEPUS_IsException(ret)) goto exception1;
      flags = LEPUS_PROP_C_W_E | LEPUS_PROP_THROW;
      if (desc.flags & LEPUS_PROP_GETSET) {
        if (JS_DefinePropertyValue_GC(ctx, ret, JS_ATOM_get, desc.getter,
                                      flags) < 0 ||
            JS_DefinePropertyValue_GC(ctx, ret, JS_ATOM_set, desc.setter,
                                      flags) < 0)
          goto exception1;
      } else {
        if (JS_DefinePropertyValue_GC(ctx, ret, JS_ATOM_value, desc.value,
                                      flags) < 0 ||
            JS_DefinePropertyValue_GC(
                ctx, ret, JS_ATOM_writable,
                LEPUS_NewBool(ctx, (desc.flags & LEPUS_PROP_WRITABLE) != 0),
                flags) < 0)
          goto exception1;
      }
      if (JS_DefinePropertyValue_GC(
              ctx, ret, JS_ATOM_enumerable,
              LEPUS_NewBool(ctx, (desc.flags & LEPUS_PROP_ENUMERABLE) != 0),
              flags) < 0 ||
          JS_DefinePropertyValue_GC(
              ctx, ret, JS_ATOM_configurable,
              LEPUS_NewBool(ctx, (desc.flags & LEPUS_PROP_CONFIGURABLE) != 0),
              flags) < 0)
        goto exception1;
    }
  }
  return ret;

exception1:
exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_object_getOwnPropertyDescriptors(LEPUSContext *ctx,
                                                      LEPUSValueConst this_val,
                                                      int argc,
                                                      LEPUSValueConst *argv) {
  // getOwnPropertyDescriptors(obj)
  LEPUSValue obj, r;
  LEPUSObject *p;
  LEPUSPropertyEnum *props = nullptr;
  HandleScope func_scope(ctx, &props, HANDLE_TYPE_HEAP_OBJ);
  uint32_t len, i;

  r = LEPUS_UNDEFINED;
  func_scope.PushHandle(&r, HANDLE_TYPE_LEPUS_VALUE);
  obj = JS_ToObject_GC(ctx, argv[0]);
  if (LEPUS_IsException(obj)) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);

  p = LEPUS_VALUE_GET_OBJ(obj);
  if (JS_GetOwnPropertyNamesInternal(
          ctx, &props, &len, p, LEPUS_GPN_STRING_MASK | LEPUS_GPN_SYMBOL_MASK))
    goto exception;
  r = JS_NewObject_GC(ctx);
  if (LEPUS_IsException(r)) goto exception;
  for (i = 0; i < len; i++) {
    LEPUSValue atomValue, desc;
    LEPUSValueConst args[2];

    atomValue = JS_AtomToValue_GC(ctx, props[i].atom);
    if (LEPUS_IsException(atomValue)) goto exception;
    HandleScope block_scope(ctx, &atomValue, HANDLE_TYPE_LEPUS_VALUE);
    args[0] = obj;
    args[1] = atomValue;
    block_scope.PushHandle(&args[0], HANDLE_TYPE_LEPUS_VALUE);
    block_scope.PushHandle(&args[1], HANDLE_TYPE_LEPUS_VALUE);
    desc =
        js_object_getOwnPropertyDescriptor_GC(ctx, LEPUS_UNDEFINED, 2, args, 0);
    block_scope.PushHandle(&desc, HANDLE_TYPE_LEPUS_VALUE);
    if (LEPUS_IsException(desc)) goto exception;
    if (!LEPUS_IsUndefined(desc)) {
      if (JS_DefinePropertyValue_GC(ctx, r, props[i].atom, desc,
                                    LEPUS_PROP_C_W_E | LEPUS_PROP_THROW) < 0)
        goto exception;
    }
  }
  return r;

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue JS_GetOwnPropertyNames2(LEPUSContext *ctx,
                                          LEPUSValueConst obj1, int flags,
                                          int kind) {
  LEPUSValue obj, r, val, key = LEPUS_UNDEFINED, value = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &key, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSObject *p;
  LEPUSPropertyEnum *atoms = nullptr;
  func_scope.PushHandle(&atoms, HANDLE_TYPE_HEAP_OBJ);
  uint32_t len, i, j;

  r = LEPUS_UNDEFINED;
  val = LEPUS_UNDEFINED;
  func_scope.PushHandle(&r, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  obj = JS_ToObject_GC(ctx, obj1);
  if (LEPUS_IsException(obj)) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_OBJ(obj);
  if (JS_GetOwnPropertyNamesInternal(ctx, &atoms, &len, p,
                                     flags & ~LEPUS_GPN_ENUM_ONLY))
    goto exception;
  r = JS_NewArray_GC(ctx);
  if (LEPUS_IsException(r)) goto exception;
  LEPUSPropertyDescriptor desc;
  for (j = i = 0; i < len; i++) {
    JSAtom atom = atoms[i].atom;
    if (flags & LEPUS_GPN_ENUM_ONLY) {
      int res;

      /* Check if property is still enumerable */
      res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom);
      if (res < 0) goto exception;
      if (!res) continue;
      if (!(desc.flags & LEPUS_PROP_ENUMERABLE)) continue;
    }
    switch (kind) {
      default:
      case JS_ITERATOR_KIND_KEY:
        val = JS_AtomToValue_GC(ctx, atom);
        if (LEPUS_IsException(val)) goto exception;
        break;
      case JS_ITERATOR_KIND_VALUE:
        val = JS_GetPropertyInternal_GC(ctx, obj, atom, obj, 0);
        if (LEPUS_IsException(val)) goto exception;
        break;
      case JS_ITERATOR_KIND_KEY_AND_VALUE:
        val = JS_NewArray_GC(ctx);
        if (LEPUS_IsException(val)) goto exception;
        key = JS_AtomToValue_GC(ctx, atom);
        if (LEPUS_IsException(key)) goto exception1;
        if (JS_CreateDataPropertyUint32(ctx, val, 0, key, LEPUS_PROP_THROW) < 0)
          goto exception1;
        value = JS_GetPropertyInternal_GC(ctx, obj, atom, obj, 0);
        if (LEPUS_IsException(value)) goto exception1;
        func_scope.PushHandle(&value, HANDLE_TYPE_LEPUS_VALUE);
        if (JS_CreateDataPropertyUint32(ctx, val, 1, value, LEPUS_PROP_THROW) <
            0)
          goto exception1;
        break;
    }
    if (JS_CreateDataPropertyUint32(ctx, r, j++, val, 0) < 0) goto exception;
  }
  goto done;

exception1:
exception:
  r = LEPUS_EXCEPTION;
done:
  return r;
}

static LEPUSValue js_object_getOwnPropertyNames(LEPUSContext *ctx,
                                                LEPUSValueConst this_val,
                                                int argc,
                                                LEPUSValueConst *argv) {
  return JS_GetOwnPropertyNames2(ctx, argv[0], LEPUS_GPN_STRING_MASK,
                                 JS_ITERATOR_KIND_KEY);
}

static LEPUSValue js_object_getOwnPropertySymbols(LEPUSContext *ctx,
                                                  LEPUSValueConst this_val,
                                                  int argc,
                                                  LEPUSValueConst *argv) {
  return JS_GetOwnPropertyNames2(ctx, argv[0], LEPUS_GPN_SYMBOL_MASK,
                                 JS_ITERATOR_KIND_KEY);
}

static LEPUSValue js_object_keys(LEPUSContext *ctx, LEPUSValueConst this_val,
                                 int argc, LEPUSValueConst *argv, int kind) {
  return JS_GetOwnPropertyNames2(
      ctx, argv[0], LEPUS_GPN_ENUM_ONLY | LEPUS_GPN_STRING_MASK, kind);
}

static LEPUSValue js_object_isExtensible(LEPUSContext *ctx,
                                         LEPUSValueConst this_val, int argc,
                                         LEPUSValueConst *argv, int reflect) {
  LEPUSValueConst obj;
  int ret;
  // <Primjs begin>
  obj = JSRef2Value(ctx, argv[0]);
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  // <Primjs end>
  if (LEPUS_VALUE_IS_NOT_OBJECT(obj)) {
    if (reflect)
      return JS_ThrowTypeErrorNotAnObject(ctx);
    else
      return LEPUS_FALSE;
  }
  ret = JS_IsExtensible_GC(ctx, obj);
  if (ret < 0)
    return LEPUS_EXCEPTION;
  else
    return LEPUS_NewBool(ctx, ret);
}

static LEPUSValue js_object_preventExtensions(LEPUSContext *ctx,
                                              LEPUSValueConst this_val,
                                              int argc, LEPUSValueConst *argv,
                                              int reflect) {
  LEPUSValueConst obj;
  int ret;
  // <Primjs begin>
  obj = JSRef2Value(ctx, argv[0]);
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  // <Primjs end>
  if (LEPUS_VALUE_IS_NOT_OBJECT(obj)) {
    if (reflect)
      return JS_ThrowTypeErrorNotAnObject(ctx);
    else
      return obj;
  }
  ret = JS_PreventExtensions_GC(ctx, obj);
  if (ret < 0) return LEPUS_EXCEPTION;
  if (reflect) {
    return LEPUS_NewBool(ctx, ret);
  } else {
    if (!ret)
      return LEPUS_ThrowTypeError(
          ctx, "proxy preventExtensions handler returned false");
    return obj;
  }
}

static LEPUSValue js_object_hasOwnProperty(LEPUSContext *ctx,
                                           LEPUSValueConst this_val, int argc,
                                           LEPUSValueConst *argv) {
  HandleScope func_scope(ctx);
  LEPUSValue obj;
  JSAtom atom;
  LEPUSObject *p;
  BOOL ret;

  atom = js_value_to_atom_gc(ctx, argv[0]); /* must be done first */
  func_scope.PushLEPUSAtom(atom);

#ifdef ENABLE_LEPUSNG
  if (LEPUS_IsLepusRef(this_val)) {
    ret = JSRefHasOwnProperty(ctx, this_val, atom);
    if (ret < 0)
      return LEPUS_EXCEPTION;
    else
      return LEPUS_NewBool(ctx, ret);
  }
#endif

  if (unlikely(atom == JS_ATOM_NULL)) return LEPUS_EXCEPTION;
  obj = JS_ToObject_GC(ctx, this_val);
  if (LEPUS_IsException(obj)) {
    return obj;
  }
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_OBJ(obj);
  ret = JS_GetOwnPropertyInternal(ctx, NULL, p, atom);
  if (ret < 0)
    return LEPUS_EXCEPTION;
  else
    return LEPUS_NewBool(ctx, ret);
}

static LEPUSValue js_object_valueOf(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
  return JS_ToObject_GC(ctx, this_val);
}

static LEPUSValue js_object_toString(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv) {
  LEPUSValue obj = LEPUS_UNDEFINED, tag = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&tag, HANDLE_TYPE_LEPUS_VALUE);
  int is_array;
  JSAtom atom;
  LEPUSObject *p;

  if (LEPUS_IsNull(this_val)) {
    tag = JS_NewString_GC(ctx, "Null");
  } else if (LEPUS_IsUndefined(this_val)) {
    tag = JS_NewString_GC(ctx, "Undefined");
  } else {
    obj = JS_ToObject_GC(ctx, this_val);
    if (LEPUS_IsException(obj)) return obj;
    is_array = JS_IsArray_GC(ctx, obj);
    if (is_array < 0) {
      return LEPUS_EXCEPTION;
    }
    if (is_array) {
      atom = JS_ATOM_Array;
    } else if (LEPUS_IsFunction(ctx, obj)) {
      atom = JS_ATOM_Function;
    } else {
      p = LEPUS_VALUE_GET_OBJ(obj);
      switch (p->class_id) {
        case JS_CLASS_STRING:
        case JS_CLASS_ARGUMENTS:
        case JS_CLASS_MAPPED_ARGUMENTS:
        case JS_CLASS_ERROR:
        case JS_CLASS_BOOLEAN:
        case JS_CLASS_NUMBER:
        case JS_CLASS_DATE:
        case JS_CLASS_REGEXP:
          atom = ctx->rt->class_array[p->class_id].class_name;
          break;
        default:
          atom = JS_ATOM_Object;
          break;
      }
    }
    tag =
        JS_GetPropertyInternal_GC(ctx, obj, JS_ATOM_Symbol_toStringTag, obj, 0);
    if (LEPUS_IsException(tag)) return LEPUS_EXCEPTION;
    if (!LEPUS_IsString(tag)) {
      tag = JS_AtomToString_GC(ctx, atom);
    }
  }
  return JS_ConcatString3(ctx, "[object ", tag, "]");
}

static LEPUSValue js_object_toLocaleString(LEPUSContext *ctx,
                                           LEPUSValueConst this_val, int argc,
                                           LEPUSValueConst *argv) {
  return JS_Invoke_GC(ctx, this_val, JS_ATOM_toString, 0, NULL);
}

static LEPUSValue js_object_assign(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
  // Object.assign(obj, source1)
  LEPUSValue obj, s;
  int i;

  s = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &s, HANDLE_TYPE_LEPUS_VALUE);
  obj = JS_ToObject_expect_lepusref(ctx, argv[0]);
  if (LEPUS_IsException(obj)) goto exception;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  for (i = 1; i < argc; i++) {
    if (!LEPUS_IsNull(argv[i]) && !LEPUS_IsUndefined(argv[i])) {
      s = JS_ToObject_expect_lepusref(ctx, argv[i]);
      if (LEPUS_IsException(s)) goto exception;
      if (JS_CopyDataProperties(ctx, obj, s, LEPUS_UNDEFINED, TRUE))
        goto exception;
    }
  }
  return obj;
exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_object_seal(LEPUSContext *ctx, LEPUSValueConst this_val,
                                 int argc, LEPUSValueConst *argv,
                                 int freeze_flag) {
  LEPUSValueConst obj = argv[0];
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSObject *p;
  LEPUSPropertyEnum *props = nullptr;
  func_scope.PushHandle(&props, HANDLE_TYPE_HEAP_OBJ);
  uint32_t len, i;
  int flags, desc_flags, res;
  // <Primjs begin>
  obj = JSRef2Value(ctx, obj);
  // <Primjs end>

  if (!LEPUS_IsObject(obj)) return obj;

  res = JS_PreventExtensions_GC(ctx, obj);
  if (res < 0) return LEPUS_EXCEPTION;
  if (!res) {
    return LEPUS_ThrowTypeError(
        ctx, "proxy preventExtensions handler returned false");
  }

  p = LEPUS_VALUE_GET_OBJ(obj);
  flags = LEPUS_GPN_STRING_MASK | LEPUS_GPN_SYMBOL_MASK;
  if (JS_GetOwnPropertyNamesInternal(ctx, &props, &len, p, flags))
    return LEPUS_EXCEPTION;

  for (i = 0; i < len; i++) {
    LEPUSPropertyDescriptor desc;
    JSAtom prop = props[i].atom;

    desc_flags = LEPUS_PROP_THROW | LEPUS_PROP_HAS_CONFIGURABLE;
    if (freeze_flag) {
      res = JS_GetOwnPropertyInternal(ctx, &desc, p, prop);
      if (res < 0) goto exception;
      if (res) {
        if (desc.flags & LEPUS_PROP_WRITABLE)
          desc_flags |= LEPUS_PROP_HAS_WRITABLE;
      }
    }
    if (JS_DefineProperty_GC(ctx, obj, prop, LEPUS_UNDEFINED, LEPUS_UNDEFINED,
                             LEPUS_UNDEFINED, desc_flags) < 0)
      goto exception;
  }
  return js_object_preventExtensions(ctx, LEPUS_UNDEFINED, 1, argv, 0);

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_object_isSealed(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv, int is_frozen) {
  LEPUSValueConst obj = argv[0];
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSObject *p;
  LEPUSPropertyEnum *props = nullptr;
  func_scope.PushHandle(&props, HANDLE_TYPE_HEAP_OBJ);
  uint32_t len, i;
  int flags, res;
  // <Primjs begin>
  obj = JSRef2Value(ctx, obj);
  // <Primjs end>

  if (!LEPUS_IsObject(obj)) return LEPUS_TRUE;

  p = LEPUS_VALUE_GET_OBJ(obj);
  flags = LEPUS_GPN_STRING_MASK | LEPUS_GPN_SYMBOL_MASK;
  if (JS_GetOwnPropertyNamesInternal(ctx, &props, &len, p, flags))
    return LEPUS_EXCEPTION;

  for (i = 0; i < len; i++) {
    LEPUSPropertyDescriptor desc;
    JSAtom prop = props[i].atom;

    res = JS_GetOwnPropertyInternal(ctx, &desc, p, prop);
    if (res < 0) goto exception;
    if (res) {
      if ((desc.flags & LEPUS_PROP_CONFIGURABLE) ||
          (is_frozen && (desc.flags & LEPUS_PROP_WRITABLE))) {
        res = FALSE;
        goto done;
      }
    }
  }
  res = JS_IsExtensible_GC(ctx, obj);
  if (res < 0) return LEPUS_EXCEPTION;
  res ^= 1;
done:
  return LEPUS_NewBool(ctx, res);

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_object_fromEntries(LEPUSContext *ctx,
                                        LEPUSValueConst this_val, int argc,
                                        LEPUSValueConst *argv) {
  LEPUSValue obj, iter, next_method = LEPUS_UNDEFINED;
  LEPUSValueConst iterable;
  BOOL done;

  /*  RequireObjectCoercible() not necessary because it is tested in
      JS_GetIterator() by JS_GetPropertyInternal_GC() */
  // <Primjs begin>
  iterable = JSRef2Value(ctx, argv[0]);
  HandleScope func_scope(ctx, &iterable, HANDLE_TYPE_LEPUS_VALUE);
  // <Primjs end>

  obj = JS_NewObject_GC(ctx);
  if (LEPUS_IsException(obj)) return obj;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);

  iter = JS_GetIterator(ctx, iterable, FALSE);
  if (LEPUS_IsException(iter)) goto fail;
  func_scope.PushHandle(&iter, HANDLE_TYPE_LEPUS_VALUE);
  next_method = JS_GetPropertyInternal_GC(ctx, iter, JS_ATOM_next, iter, 0);
  if (LEPUS_IsException(next_method)) goto fail;
  func_scope.PushHandle(&next_method, HANDLE_TYPE_LEPUS_VALUE);

  for (;;) {
    HandleScope block_scope(ctx->rt);
    LEPUSValue key, value, item;
    item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
    block_scope.PushHandle(&item, HANDLE_TYPE_LEPUS_VALUE);
    if (LEPUS_IsException(item)) goto fail;
    if (done) {
      break;
    }

    key = LEPUS_UNDEFINED;
    value = LEPUS_UNDEFINED;
    block_scope.PushHandle(&key, HANDLE_TYPE_LEPUS_VALUE);
    block_scope.PushHandle(&value, HANDLE_TYPE_LEPUS_VALUE);
    if (!LEPUS_IsObject(item)) {
      JS_ThrowTypeErrorNotAnObject(ctx);
      goto fail1;
    }
    key = JS_GetPropertyUint32_GC(ctx, item, 0);
    if (LEPUS_IsException(key)) goto fail1;
    value = JS_GetPropertyUint32_GC(ctx, item, 1);
    if (LEPUS_IsException(value)) {
      goto fail1;
    }
    if (JS_DefinePropertyValueValue_GC(
            ctx, obj, key, value, LEPUS_PROP_C_W_E | LEPUS_PROP_THROW) < 0) {
    fail1:
      goto fail;
    }
  }
  return obj;
fail:
  if (LEPUS_IsObject(iter)) {
    /* close the iterator object, preserving pending exception */
    JS_IteratorClose(ctx, iter, TRUE);
  }
  return LEPUS_EXCEPTION;
}

#if 0
/* Note: corresponds to ECMA spec: CreateDataPropertyOrThrow() */
static LEPUSValue js_object___setOwnProperty(LEPUSContext *ctx, LEPUSValueConst this_val,
                                          int argc, LEPUSValueConst *argv) {
    int ret;
    ret = JS_DefinePropertyValueValue_GC(ctx, argv[0], argv[1],
                                      argv[2],
                                      LEPUS_PROP_C_W_E | LEPUS_PROP_THROW);
    if (ret < 0)
        return LEPUS_EXCEPTION;
    else
        return LEPUS_NewBool(ctx, ret);
}

static LEPUSValue js_object___toObject(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
    return JS_ToObject_GC(ctx, argv[0]);
}

static LEPUSValue js_object___toPrimitive(LEPUSContext *ctx, LEPUSValueConst this_val,
                                       int argc, LEPUSValueConst *argv) {
    int hint = HINT_NONE;

    if (LEPUS_VALUE_IS_INT(argv[1]))
        hint = LEPUS_VALUE_GET_INT(argv[1]);

    return JS_ToPrimitive(ctx, argv[0], hint);
}
#endif

/* return an empty string if not an object */
static LEPUSValue js_object___getClass(LEPUSContext *ctx,
                                       LEPUSValueConst this_val, int argc,
                                       LEPUSValueConst *argv) {
  JSAtom atom;
  LEPUSObject *p;
  int64_t tag;
  int class_id;
  // <Primjs begin>
#ifdef ENABLE_LEPUSNG
  LEPUSValue obj = argv[0];
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_IsLepusRef(obj)) {
    if (JS_LepusRefIsArray(ctx->rt, obj)) {
      atom = ctx->rt->class_array[JS_CLASS_ARRAY].class_name;
    } else if (JS_LepusRefIsTable(ctx->rt, obj)) {
      atom = ctx->rt->class_array[JS_CLASS_OBJECT].class_name;
    } else {
      atom = JS_ATOM_empty_string;
    }
    return JS_AtomToString_GC(ctx, atom);
  }
#endif
  // <Primjs end>

  tag = LEPUS_VALUE_GET_NORM_TAG(argv[0]);
  if (tag == LEPUS_TAG_OBJECT) {
    p = LEPUS_VALUE_GET_OBJ(argv[0]);
    class_id = p->class_id;
    if (class_id == JS_CLASS_PROXY && LEPUS_IsFunction(ctx, argv[0]))
      class_id = JS_CLASS_BYTECODE_FUNCTION;
    atom = ctx->rt->class_array[class_id].class_name;
  } else {
    atom = JS_ATOM_empty_string;
  }
  return JS_AtomToString_GC(ctx, atom);
}

static LEPUSValue js_object_is(LEPUSContext *ctx, LEPUSValueConst this_val,
                               int argc, LEPUSValueConst *argv) {
  return LEPUS_NewBool(ctx, js_same_value(ctx, argv[0], argv[1]));
}

// <Primjs begin>
LEPUSValue JS_DeepEqual_GC(LEPUSContext *ctx, LEPUSValueConst obj1,
                           LEPUSValueConst obj2) {
#ifdef ENABLE_LEPUSNG
  if (LEPUS_IsLepusRef(obj1) && LEPUS_IsLepusRef(obj2)) {
    BOOL ret = ctx->rt->js_callbacks_.lepus_ref_equal(obj1, obj2);
    return LEPUS_NewBool(ctx, ret);
  }
#endif

  BOOL ret = js_same_value(ctx, obj1, obj2);
  if (ret || (!LEPUS_IsObject(obj1) && !LEPUS_IsLepusRef(obj1)) ||
      (!LEPUS_IsObject(obj2) && !LEPUS_IsLepusRef(obj2))) {
    return LEPUS_NewBool(ctx, ret);
  }
  obj1 = JSRef2Value(ctx, obj1);
  obj2 = JSRef2Value(ctx, obj2);
  // if both obj1 and obj2 are not object, return FALSE
  if (!LEPUS_IsObject(obj1) || !LEPUS_IsObject(obj2)) {
    return LEPUS_NewBool(ctx, FALSE);
  }
  // for object compare
  LEPUSObject *p1 = LEPUS_VALUE_GET_OBJ(obj1);
  LEPUSObject *p2 = LEPUS_VALUE_GET_OBJ(obj2);
  uint32_t i;
  JSShape *sh1, *sh2;
  JSShapeProperty *pr1, *pr2;
  LEPUSValue val1 = LEPUS_UNDEFINED, val2 = LEPUS_UNDEFINED,
             result = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &val1, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&val2, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&result, HANDLE_TYPE_LEPUS_VALUE);
  JSAtom atom;
  if (p1->class_id != p2->class_id) {
    goto fail;
  }

  if (p1->class_id != JS_CLASS_ARRAY && p1->class_id != JS_CLASS_OBJECT) {
    goto fail;
  }

  if (p1->class_id == JS_CLASS_ARRAY) {
    uint32_t len1, len2;
    if (js_get_length32_gc(ctx, &len1, obj1) ||
        js_get_length32_gc(ctx, &len2, obj2)) {
      goto fail;
    }
    if (len1 != len2) goto fail;

    for (i = 0; i < len1; i++) {
      val1 = JS_GetPropertyUint32_GC(ctx, obj1, i);
      val2 = JS_GetPropertyUint32_GC(ctx, obj2, i);
      if (LEPUS_IsException(val1) || LEPUS_IsException(val2)) goto fail;
      result = JS_DeepEqual_GC(ctx, val1, val2);
      if (!LEPUS_VALUE_GET_BOOL(result)) goto fail;
    }

    goto ok;

  } else {
    sh1 = p1->shape;
    sh2 = p2->shape;
    if (sh1->prop_count != sh2->prop_count) goto fail;
    for (i = 0, pr1 = get_shape_prop(sh1); i < sh1->prop_count; i++, pr1++) {
      atom = pr1->atom;
      if (atom != JS_ATOM_NULL && JS_AtomIsString(ctx, atom) &&
          (pr1->flags & LEPUS_PROP_ENUMERABLE)) {
        val2 = JS_GetPropertyInternal_GC(ctx, obj2, atom, obj2, 0);
        if (LEPUS_IsException(val2)) goto fail;
        val1 = p1->prop[i].u.value;
        result = JS_DeepEqual_GC(ctx, val1, val2);
        if (!LEPUS_VALUE_GET_BOOL(result)) goto fail;
      }
    }
    goto ok;
  }

fail:
  return LEPUS_NewBool(ctx, 0);

ok:
  return LEPUS_NewBool(ctx, 1);
}

void JS_IterateObject_GC(LEPUSContext *ctx, LEPUSValue obj,
                         IterateObject callback, void *pfunc, void *raw_data) {
  if (!LEPUS_IsObject(obj)) return;
  LEPUSObject *p = LEPUS_VALUE_GET_OBJ(obj);
  uint32_t i, len;
  LEPUSValue val = LEPUS_UNDEFINED;
  JSShape *sh;
  JSShapeProperty *pr;
  JSAtom atom;
  LEPUSValue atomValue = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &atomValue, HANDLE_TYPE_LEPUS_VALUE);
  if (p->class_id == JS_CLASS_ARRAY) {
    if (js_get_length32_gc(ctx, &len, obj)) return;
    for (i = 0; i < len; i++) {
      val = JS_GetPropertyUint32_GC(ctx, obj, i);
      if (LEPUS_IsException(val)) return;
      callback(ctx, LEPUS_NewInt32(ctx, i), val, pfunc, raw_data);
    }
  } else {
    sh = p->shape;
    for (i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) {
      atom = pr->atom;
      if (atom != JS_ATOM_NULL && JS_AtomIsString(ctx, atom) &&
          (pr->flags & LEPUS_PROP_ENUMERABLE)) {
        if (pr->flags & LEPUS_PROP_TMASK) {
          LEPUS_ThrowTypeError(ctx, "only value properties are supported");
          return;
        }
        atomValue = JS_AtomToValue_GC(ctx, atom);
        callback(ctx, atomValue, p->prop[i].u.value, pfunc, raw_data);
      }
    }
  }
  return;
}
int JS_GetLength_GC(LEPUSContext *ctx, LEPUSValue val) {
  int64_t tag = LEPUS_VALUE_GET_NORM_TAG(val);
  if (tag == LEPUS_TAG_STRING) {
    JSString *p = LEPUS_VALUE_GET_STRING(val);
    return p->len;
  } else if (tag == LEPUS_TAG_SEPARABLE_STRING) {
    return JS_GetSeparableString(val)->len;
  } else if (tag == LEPUS_TAG_OBJECT) {
    LEPUSObject *p = LEPUS_VALUE_GET_OBJ(val);
    uint32_t len;
    if (p->class_id == JS_CLASS_ARRAY) {
      if (js_get_length32_gc(ctx, &len, val)) return -1;
      return len;
    } else {
      return p->shape->prop_count;
    }
  }
#ifdef ENABLE_LEPUSNG
  // <Primjs begin>
  else if (tag == LEPUS_TAG_LEPUS_REF) {
    if (ctx->rt->js_callbacks_.get_length) {
      return ctx->rt->js_callbacks_.get_length(ctx, val);
    }
  }
#endif
  // <Primjs end>
  return 0;
}

#if 0
static LEPUSValue js_object___getObjectData(LEPUSContext *ctx, LEPUSValueConst this_val,
                                         int argc, LEPUSValueConst *argv) {
    return JS_GetObjectData(ctx, argv[0]);
}

static LEPUSValue js_object___setObjectData(LEPUSContext *ctx, LEPUSValueConst this_val,
                                         int argc, LEPUSValueConst *argv) {
    if (JS_SetObjectData(ctx, argv[0], argv[1]))
        return LEPUS_EXCEPTION;
    return argv[1];
}

static LEPUSValue js_object___toPropertyKey(LEPUSContext *ctx, LEPUSValueConst this_val,
                                         int argc, LEPUSValueConst *argv) {
    return JS_ToPropertyKey_GC(ctx, argv[0]);
}

static LEPUSValue js_object___isObject(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
    return LEPUS_NewBool(ctx, LEPUS_IsObject(argv[0]));
}

static LEPUSValue js_object___isSameValueZero(LEPUSContext *ctx, LEPUSValueConst this_val,
                                           int argc, LEPUSValueConst *argv) {
    return LEPUS_NewBool(ctx, js_same_value_zero(ctx, argv[0], argv[1]));
}

static LEPUSValue js_object___isConstructor(LEPUSContext *ctx, LEPUSValueConst this_val,
                                         int argc, LEPUSValueConst *argv) {
    return LEPUS_NewBool(ctx, LEPUS_IsConstructor(ctx, argv[0]));
}
#endif

static LEPUSValue JS_SpeciesConstructor(LEPUSContext *ctx, LEPUSValueConst obj,
                                        LEPUSValueConst defaultConstructor) {
  LEPUSValue ctor, species;

  if (!LEPUS_IsObject(obj)) return JS_ThrowTypeErrorNotAnObject(ctx);
  ctor = JS_GetPropertyInternal_GC(ctx, obj, JS_ATOM_constructor, obj, 0);
  if (LEPUS_IsException(ctor)) return ctor;
  if (LEPUS_IsUndefined(ctor)) return defaultConstructor;
  if (!LEPUS_IsObject(ctor)) {
    return JS_ThrowTypeErrorNotAnObject(ctx);
  }
  species =
      JS_GetPropertyInternal_GC(ctx, ctor, JS_ATOM_Symbol_species, ctor, 0);
  if (LEPUS_IsException(species)) return species;
  if (LEPUS_IsUndefined(species) || LEPUS_IsNull(species))
    return defaultConstructor;
  if (!LEPUS_IsConstructor(ctx, species)) {
    return LEPUS_ThrowTypeError(ctx, "not a constructor");
  }
  return species;
}

#if 0
static LEPUSValue js_object___speciesConstructor(LEPUSContext *ctx, LEPUSValueConst this_val,
                                              int argc, LEPUSValueConst *argv) {
    return JS_SpeciesConstructor(ctx, argv[0], argv[1]);
}
#endif

static LEPUSValue js_object_get___proto__(LEPUSContext *ctx,
                                          LEPUSValueConst this_val) {
  LEPUSValue val, ret;

  val = JS_ToObject_GC(ctx, this_val);
  if (LEPUS_IsException(val)) return val;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  ret = JS_GetPrototype_GC(ctx, val);
  return ret;
}

static LEPUSValue js_object_set___proto__(LEPUSContext *ctx,
                                          LEPUSValueConst this_val,
                                          LEPUSValueConst proto) {
  if (LEPUS_IsUndefined(this_val) || LEPUS_IsNull(this_val))
    return JS_ThrowTypeErrorNotAnObject(ctx);
  if (!LEPUS_IsObject(proto) && !LEPUS_IsNull(proto)) return LEPUS_UNDEFINED;
  if (JS_SetPrototypeInternal_GC(ctx, this_val, proto, TRUE) < 0)
    return LEPUS_EXCEPTION;
  else
    return LEPUS_UNDEFINED;
}

static LEPUSValue js_object_isPrototypeOf(LEPUSContext *ctx,
                                          LEPUSValueConst this_val, int argc,
                                          LEPUSValueConst *argv) {
  LEPUSValue obj;
  LEPUSValueConst v;
  int max_depth = 1000, res = -1;

  v = argv[0];
  HandleScope func_scope(ctx, &v, HANDLE_TYPE_LEPUS_VALUE);
  if (!LEPUS_IsObject(v)) return LEPUS_FALSE;
  obj = JS_ToObject_GC(ctx, this_val);
  if (LEPUS_IsException(obj)) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  while (--max_depth > 0) {
    v = JS_GetPrototype_GC(ctx, v);
    if (LEPUS_IsException(v)) goto exception;
    if (LEPUS_IsNull(v)) {
      res = FALSE;
      break;
    }
    if (js_strict_eq2(ctx, obj, v, JS_EQ_STRICT)) {
      res = TRUE;
      break;
    }
  }
  if (res < 0)
    return LEPUS_ThrowInternalError(ctx, "prototype chain cycle");
  else
    return LEPUS_NewBool(ctx, res);

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_object_propertyIsEnumerable(LEPUSContext *ctx,
                                                 LEPUSValueConst this_val,
                                                 int argc,
                                                 LEPUSValueConst *argv) {
  LEPUSValue obj, res = LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, &res, HANDLE_TYPE_LEPUS_VALUE);
  JSAtom prop = JS_ATOM_NULL;
  LEPUSPropertyDescriptor desc;
  int has_prop;

  obj = JS_ToObject_GC(ctx, this_val);
  if (LEPUS_IsException(obj)) goto exception;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  prop = js_value_to_atom_gc(ctx, argv[0]);
  if (unlikely(prop == JS_ATOM_NULL)) goto exception;

  func_scope.PushLEPUSAtom(prop);
  has_prop =
      JS_GetOwnPropertyInternal(ctx, &desc, LEPUS_VALUE_GET_OBJ(obj), prop);
  if (has_prop < 0) goto exception;
  if (has_prop) {
    res = LEPUS_NewBool(ctx, (desc.flags & LEPUS_PROP_ENUMERABLE) != 0);
  } else {
    res = LEPUS_FALSE;
  }

exception:
  return res;
}

static LEPUSValue js_object___lookupGetter__(LEPUSContext *ctx,
                                             LEPUSValueConst this_val, int argc,
                                             LEPUSValueConst *argv,
                                             int setter) {
  LEPUSValue obj, res = LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, &res, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst v = LEPUS_UNDEFINED;
  func_scope.PushHandle(&v, HANDLE_TYPE_LEPUS_VALUE);
  JSAtom prop = JS_ATOM_NULL;
  LEPUSPropertyDescriptor desc;
  int has_prop;

  obj = JS_ToObject_GC(ctx, this_val);
  if (LEPUS_IsException(obj)) goto exception;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  prop = js_value_to_atom_gc(ctx, argv[0]);
  if (unlikely(prop == JS_ATOM_NULL)) goto exception;
  func_scope.PushLEPUSAtom(prop);

  for (v = obj;;) {
    has_prop =
        JS_GetOwnPropertyInternal(ctx, &desc, LEPUS_VALUE_GET_OBJ(v), prop);
    if (has_prop < 0) goto exception;
    if (has_prop) {
      if (desc.flags & LEPUS_PROP_GETSET)
        res = setter ? desc.setter : desc.getter;
      else
        res = LEPUS_UNDEFINED;
      break;
    }
    v = JS_GetPrototype_GC(ctx, v);
    if (LEPUS_IsException(v)) goto exception;
    if (LEPUS_IsNull(v)) {
      res = LEPUS_UNDEFINED;
      break;
    }
  }

exception:
  return res;
}

static const LEPUSCFunctionListEntry js_object_funcs[] = {
    LEPUS_CFUNC_DEF("create", 2, js_object_create),
    LEPUS_CFUNC_MAGIC_DEF("getPrototypeOf", 1, js_object_getPrototypeOf, 0),
    LEPUS_CFUNC_DEF("setPrototypeOf", 2, js_object_setPrototypeOf),
    LEPUS_CFUNC_MAGIC_DEF("defineProperty", 3, js_object_defineProperty, 0),
    LEPUS_CFUNC_DEF("defineProperties", 2, js_object_defineProperties),
    LEPUS_CFUNC_DEF("getOwnPropertyNames", 1, js_object_getOwnPropertyNames),
    LEPUS_CFUNC_DEF("getOwnPropertySymbols", 1,
                    js_object_getOwnPropertySymbols),
    LEPUS_CFUNC_MAGIC_DEF("keys", 1, js_object_keys, JS_ITERATOR_KIND_KEY),
    LEPUS_CFUNC_MAGIC_DEF("values", 1, js_object_keys, JS_ITERATOR_KIND_VALUE),
    LEPUS_CFUNC_MAGIC_DEF("entries", 1, js_object_keys,
                          JS_ITERATOR_KIND_KEY_AND_VALUE),
    LEPUS_CFUNC_MAGIC_DEF("isExtensible", 1, js_object_isExtensible, 0),
    LEPUS_CFUNC_MAGIC_DEF("preventExtensions", 1, js_object_preventExtensions,
                          0),
    LEPUS_CFUNC_MAGIC_DEF("getOwnPropertyDescriptor", 2,
                          js_object_getOwnPropertyDescriptor_GC, 0),
    LEPUS_CFUNC_DEF("getOwnPropertyDescriptors", 1,
                    js_object_getOwnPropertyDescriptors),
    LEPUS_CFUNC_DEF("is", 2, js_object_is),
    LEPUS_CFUNC_DEF("assign", 2, js_object_assign),
    LEPUS_CFUNC_MAGIC_DEF("seal", 1, js_object_seal, 0),
    LEPUS_CFUNC_MAGIC_DEF("freeze", 1, js_object_seal, 1),
    LEPUS_CFUNC_MAGIC_DEF("isSealed", 1, js_object_isSealed, 0),
    LEPUS_CFUNC_MAGIC_DEF("isFrozen", 1, js_object_isSealed, 1),
    LEPUS_CFUNC_DEF("__getClass", 1, js_object___getClass),
    // LEPUS_CFUNC_DEF("__isObject", 1, js_object___isObject ),
    // LEPUS_CFUNC_DEF("__isConstructor", 1, js_object___isConstructor ),
    // LEPUS_CFUNC_DEF("__toObject", 1, js_object___toObject ),
    // LEPUS_CFUNC_DEF("__setOwnProperty", 3, js_object___setOwnProperty ),
    // LEPUS_CFUNC_DEF("__toPrimitive", 2, js_object___toPrimitive ),
    // LEPUS_CFUNC_DEF("__toPropertyKey", 1, js_object___toPropertyKey ),
    // LEPUS_CFUNC_DEF("__speciesConstructor", 2,
    // js_object___speciesConstructor ), LEPUS_CFUNC_DEF("__isSameValueZero",
    // 2, js_object___isSameValueZero ), LEPUS_CFUNC_DEF("__getObjectData",
    // 1, js_object___getObjectData ), LEPUS_CFUNC_DEF("__setObjectData", 2,
    // js_object___setObjectData ),
    LEPUS_CFUNC_DEF("fromEntries", 1, js_object_fromEntries),
};

static const LEPUSCFunctionListEntry js_object_proto_funcs[] = {
    LEPUS_CFUNC_DEF("toString", 0, js_object_toString),
    LEPUS_CFUNC_DEF("toLocaleString", 0, js_object_toLocaleString),
    LEPUS_CFUNC_DEF("valueOf", 0, js_object_valueOf),
    LEPUS_CFUNC_DEF("hasOwnProperty", 1, js_object_hasOwnProperty),
    LEPUS_CFUNC_DEF("isPrototypeOf", 1, js_object_isPrototypeOf),
    LEPUS_CFUNC_DEF("propertyIsEnumerable", 1, js_object_propertyIsEnumerable),
    LEPUS_CGETSET_DEF("__proto__", js_object_get___proto__,
                      js_object_set___proto__),
    LEPUS_CFUNC_MAGIC_DEF("__defineGetter__", 2, js_object___defineGetter__, 0),
    LEPUS_CFUNC_MAGIC_DEF("__defineSetter__", 2, js_object___defineGetter__, 1),
    LEPUS_CFUNC_MAGIC_DEF("__lookupGetter__", 1, js_object___lookupGetter__, 0),
    LEPUS_CFUNC_MAGIC_DEF("__lookupSetter__", 1, js_object___lookupGetter__, 1),
};

/* Function class */

static LEPUSValue js_function_proto(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
  return LEPUS_UNDEFINED;
}

static LEPUSValue js_function_constructor(LEPUSContext *ctx,
                                          LEPUSValueConst new_target, int argc,
                                          LEPUSValueConst *argv, int magic) {
  JSFunctionKindEnum func_kind = static_cast<JSFunctionKindEnum>(magic);
  int i, n, ret;
  LEPUSValue s, proto, obj = LEPUS_UNDEFINED;
  StringBuffer b_s, *b = &b_s;

  string_buffer_init(ctx, b, 0);
  HandleScope func_scope(ctx, &b->str, HANDLE_TYPE_HEAP_OBJ);
  string_buffer_putc8(b, '(');

  if (func_kind == JS_FUNC_ASYNC || func_kind == JS_FUNC_ASYNC_GENERATOR) {
    string_buffer_puts8(b, "async ");
  }
  string_buffer_puts8(b, "function");

  if (func_kind == JS_FUNC_GENERATOR || func_kind == JS_FUNC_ASYNC_GENERATOR) {
    string_buffer_putc8(b, '*');
  }
  string_buffer_puts8(b, " anonymous(");

  n = argc - 1;
  for (i = 0; i < n; i++) {
    if (i != 0) {
      string_buffer_putc8(b, ',');
    }
    if (string_buffer_concat_value(b, argv[i])) goto fail;
  }
  string_buffer_puts8(b, "\n) {\n");
  if (n >= 0) {
    if (string_buffer_concat_value(b, argv[n])) goto fail;
  }
  string_buffer_puts8(b, "\n})");
  s = string_buffer_end(b);
  if (LEPUS_IsException(s)) goto fail1;
  func_scope.PushHandle(&s, HANDLE_TYPE_LEPUS_VALUE);

  obj = JS_EvalObject(ctx, ctx->global_obj, s, LEPUS_EVAL_TYPE_INDIRECT, -1);
  if (LEPUS_IsException(obj)) goto fail1;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  if (!LEPUS_IsUndefined(new_target)) {
    /* set the prototype */
    proto = js_get_prototype_from_ctor(ctx, new_target, LEPUS_UNDEFINED);
    if (LEPUS_IsException(proto)) goto fail1;
    func_scope.PushHandle(&proto, HANDLE_TYPE_LEPUS_VALUE);
    if (!LEPUS_IsUndefined(proto)) {
      ret = JS_SetPrototypeInternal_GC(ctx, obj, proto, TRUE);
      if (ret < 0) goto fail1;
    }
  }
  return obj;

fail:
  b->str = NULL;
fail1:
  return LEPUS_EXCEPTION;
}

__exception int js_get_length32_gc(LEPUSContext *ctx, uint32_t *pres,
                                   LEPUSValueConst obj) {
  LEPUSValue len_val;
  len_val = JS_GetPropertyInternal_GC(ctx, obj, JS_ATOM_length, obj, 0);
  if (LEPUS_IsException(len_val)) {
    *pres = 0;
    return -1;
  }
  return JS_ToUint32Free(ctx, pres, len_val);
}

LEPUSValue js_get_length(LEPUSContext *ctx, LEPUSValueConst obj) {
  LEPUSValue len_val;

  LEPUSObject *p = (LEPUSObject *)LEPUS_VALUE_GET_OBJ(obj);
  // Array's length must exist and the idx is 0
  if (likely(LEPUS_VALUE_IS_OBJECT(obj) && p->class_id == JS_CLASS_ARRAY)) {
    len_val = p->prop[0].u.value;
  } else {
    len_val = JS_GetPropertyInternal_GC(ctx, obj, JS_ATOM_length, obj, 0);
  }

  return len_val;
}

static __exception int js_get_length64(LEPUSContext *ctx, int64_t *pres,
                                       LEPUSValueConst obj) {
  LEPUSValue len_val;
  len_val = JS_GetPropertyInternal_GC(ctx, obj, JS_ATOM_length, obj, 0);
  if (LEPUS_IsException(len_val)) {
    *pres = 0;
    return -1;
  }
  return JS_ToLengthFree(ctx, pres, len_val);
}

/* XXX: should use ValueArray */
static LEPUSValue *build_arg_list(LEPUSContext *ctx, uint32_t *plen,
                                  LEPUSValueConst array_arg) {
  uint32_t len, i;
  LEPUSValue *tab, ret;
  LEPUSObject *p;

  if (LEPUS_VALUE_IS_NOT_OBJECT(array_arg)) {
    LEPUS_ThrowTypeError(ctx, "not a object");
    return NULL;
  }
  if (js_get_length32_gc(ctx, &len, array_arg)) return NULL;
  /* avoid allocating 0 bytes */
  tab = static_cast<LEPUSValue *>(lepus_mallocz(
      ctx, sizeof(tab[0]) * max_uint32(1, len), ALLOC_TAG_JSValueArray));
  if (!tab) return NULL;
  set_heap_obj_len(tab, 0);
  HandleScope func_scope(ctx, tab, HANDLE_TYPE_DIR_HEAP_OBJ);
  p = LEPUS_VALUE_GET_OBJ(array_arg);

  if ((p->class_id == JS_CLASS_ARRAY || p->class_id == JS_CLASS_ARGUMENTS) &&
      p->fast_array && len == p->u.array.count) {
    for (i = 0; i < len; i++) {
      tab[i] = p->u.array.u.values[i];
    }
    set_heap_obj_len(tab, len);
  } else {
    for (i = 0; i < len; i++) {
      ret = JS_GetPropertyUint32_GC(ctx, array_arg, i);
      if (LEPUS_IsException(ret)) {
        return NULL;
      }
      tab[i] = ret;
      set_heap_obj_len(tab, i + 1);
    }
  }
  *plen = len;
  return tab;
}

LEPUSValue js_function_apply_gc(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv, int magic) {
  LEPUSValueConst this_arg, array_arg;
  uint32_t len;
  LEPUSValue *tab, ret;

  if (check_function(ctx, this_val)) return LEPUS_EXCEPTION;
  this_arg = argv[0];
  array_arg = argv[1];
  if (LEPUS_VALUE_IS_UNDEFINED(array_arg) || LEPUS_VALUE_IS_NULL(array_arg)) {
    return JS_Call_GC(ctx, this_val, this_arg, 0, NULL);
  }
  tab = build_arg_list(ctx, &len, array_arg);
  if (!tab) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, tab, HANDLE_TYPE_DIR_HEAP_OBJ);
  if (magic) {
    ret = JS_CallConstructor2_GC(ctx, this_val, this_arg, len,
                                 reinterpret_cast<LEPUSValueConst *>(tab));
  } else {
    ret = JS_Call_GC(ctx, this_val, this_arg, len,
                     reinterpret_cast<LEPUSValueConst *>(tab));
  }
  return ret;
}

static LEPUSValue js_function_call(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
  if (argc <= 0) {
    return JS_CallInternalTI_GC(ctx, this_val, LEPUS_UNDEFINED, LEPUS_UNDEFINED,
                                0, NULL, JS_CALL_FLAG_COPY_ARGV);
  } else {
    return JS_CallInternalTI_GC(ctx, this_val, argv[0], LEPUS_UNDEFINED,
                                argc - 1, argv + 1, JS_CALL_FLAG_COPY_ARGV);
  }
}

static LEPUSValue js_function_bind(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
  JSBoundFunction *bf;
  LEPUSValue func_obj, name1 = LEPUS_UNDEFINED, len_val = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &name1, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&len_val, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSObject *p;
  int arg_count, i, ret;

  if (check_function(ctx, this_val)) return LEPUS_EXCEPTION;

  // https://262.ecma-international.org/6.0/#sec-boundfunctioncreate
  func_obj = JS_NewObjectProtoClass_GC(ctx, JS_GetPrototype_GC(ctx, this_val),
                                       JS_CLASS_BOUND_FUNCTION);
  if (LEPUS_IsException(func_obj)) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&func_obj, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_OBJ(func_obj);
  p->is_constructor = LEPUS_IsConstructor(ctx, this_val);
  arg_count = max_int(0, argc - 1);
  bf = static_cast<JSBoundFunction *>(
      lepus_malloc(ctx, sizeof(*bf) + arg_count * sizeof(LEPUSValue),
                   ALLOC_TAG_JSBoundFunction));
  if (!bf) goto exception;
  bf->func_obj = this_val;
  bf->this_val = argv[0];
  bf->argc = arg_count;
  for (i = 0; i < arg_count; i++) {
    bf->argv[i] = argv[i + 1];
  }
  p->u.bound_function = bf;

  ret = JS_GetOwnProperty_GC(ctx, NULL, this_val, JS_ATOM_length);
  if (ret < 0) goto exception;
  if (!ret) {
    len_val = LEPUS_NewInt32(ctx, 0);
  } else {
    len_val =
        JS_GetPropertyInternal_GC(ctx, this_val, JS_ATOM_length, this_val, 0);
    if (LEPUS_IsException(len_val)) goto exception;
    if (LEPUS_VALUE_IS_INT(len_val)) {
      int len1 = LEPUS_VALUE_GET_INT(len_val);
      if (len1 <= arg_count)
        len1 = 0;
      else
        len1 -= arg_count;
      len_val = LEPUS_NewInt32(ctx, len1);
    } else if (LEPUS_VALUE_IS_FLOAT64(len_val)) {
      double d = LEPUS_VALUE_GET_FLOAT64(len_val);
      if (isnan(d)) {
        d = 0.0;
      } else {
        d = trunc(d);
        if (d <= static_cast<double>(arg_count))
          d = 0.0;
        else
          d -= static_cast<double>(arg_count); /* also converts -0 to +0 */
      }
      len_val = LEPUS_NewFloat64(ctx, d);
    } else {
      len_val = LEPUS_NewInt32(ctx, 0);
    }
  }

  JS_DefinePropertyValue_GC(ctx, func_obj, JS_ATOM_length, len_val,
                            LEPUS_PROP_CONFIGURABLE);

  name1 = JS_GetPropertyInternal_GC(ctx, this_val, JS_ATOM_name, this_val, 0);
  if (LEPUS_IsException(name1)) goto exception;
  if (!LEPUS_IsString(name1)) {
    name1 = JS_AtomToString_GC(ctx, JS_ATOM_empty_string);
  }
  name1 = JS_ConcatString3(ctx, "bound ", name1, "");
  if (LEPUS_IsException(name1)) goto exception;
  JS_DefinePropertyValue_GC(ctx, func_obj, JS_ATOM_name, name1,
                            LEPUS_PROP_CONFIGURABLE);
  return func_obj;
exception:
  return LEPUS_EXCEPTION;
}

QJS_HIDE
LEPUSValue js_function_toString_GC(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
  LEPUSObject *p;
  JSFunctionKindEnum func_kind = JS_FUNC_NORMAL;

  if (check_function(ctx, this_val)) return LEPUS_EXCEPTION;

  p = LEPUS_VALUE_GET_OBJ(this_val);
  if (lepus_class_has_bytecode(p->class_id)) {
    LEPUSFunctionBytecode *b = p->u.func.function_bytecode;
    if (b->has_debug && b->debug.source) {
      return JS_NewStringLen_GC(ctx, b->debug.source, b->debug.source_len);
    }
    func_kind = static_cast<JSFunctionKindEnum>(b->func_kind);
  }
  {
    LEPUSValue name;
    const char *pref, *suff;

    if (p->is_class) {
      pref = "class ";
      suff = " {\n    [native code]\n}";
    } else {
      switch (func_kind) {
        default:
        case JS_FUNC_NORMAL:
          pref = "function ";
          break;
        case JS_FUNC_GENERATOR:
          pref = "function *";
          break;
        case JS_FUNC_ASYNC:
          pref = "async function ";
          break;
        case JS_FUNC_ASYNC_GENERATOR:
          pref = "async function *";
          break;
      }
      suff = "() {\n    [native code]\n}";
    }
    name = JS_GetPropertyInternal_GC(ctx, this_val, JS_ATOM_name, this_val, 0);
    if (LEPUS_IsUndefined(name))
      name = JS_AtomToString_GC(ctx, JS_ATOM_empty_string);
    return JS_ConcatString3(ctx, pref, name, suff);
  }
}

static LEPUSValue js_function_hasInstance(LEPUSContext *ctx,
                                          LEPUSValueConst this_val, int argc,
                                          LEPUSValueConst *argv) {
  int ret;
  ret = JS_OrdinaryIsInstanceOf(ctx, argv[0], this_val);
  if (ret < 0)
    return LEPUS_EXCEPTION;
  else
    return LEPUS_NewBool(ctx, ret);
}

static const LEPUSCFunctionListEntry js_function_proto_funcs[] = {
    LEPUS_CFUNC_DEF("call", 1, js_function_call),
    LEPUS_CFUNC_MAGIC_DEF("apply", 2, js_function_apply_gc, 0),
    LEPUS_CFUNC_DEF("bind", 1, js_function_bind),
    LEPUS_CFUNC_DEF("toString", 0, js_function_toString_GC),
    LEPUS_CFUNC_DEF("[Symbol.hasInstance]", 1, js_function_hasInstance),
    LEPUS_CGETSET_DEF("fileName", js_function_proto_fileName_GC, NULL),
    LEPUS_CGETSET_DEF("lineNumber", js_function_proto_lineNumber, NULL),
};

static LEPUSValue js_error_toString(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
  LEPUSValue name, msg;

  if (!LEPUS_IsObject(this_val)) return JS_ThrowTypeErrorNotAnObject(ctx);
  name = JS_GetPropertyInternal_GC(ctx, this_val, JS_ATOM_name, this_val, 0);
  HandleScope func_scope(ctx, &name, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_IsUndefined(name))
    name = JS_AtomToString_GC(ctx, JS_ATOM_Error);
  else
    name = JS_ToStringFree(ctx, name);
  if (LEPUS_IsException(name)) return LEPUS_EXCEPTION;

  msg = JS_GetPropertyInternal_GC(ctx, this_val, JS_ATOM_message, this_val, 0);
  func_scope.PushHandle(&msg, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_IsUndefined(msg))
    msg = JS_AtomToString_GC(ctx, JS_ATOM_empty_string);
  else
    msg = JS_ToStringFree(ctx, msg);
  if (LEPUS_IsException(msg)) {
    return LEPUS_EXCEPTION;
  }
  if (!JS_IsEmptyString(name) && !JS_IsEmptyString(msg))
    name = JS_ConcatString3(ctx, "", name, ": ");
  return JS_ConcatString_GC(ctx, name, msg);
}

static const LEPUSCFunctionListEntry js_error_proto_funcs[] = {
    LEPUS_CFUNC_DEF("toString", 0, js_error_toString),
    LEPUS_PROP_STRING_DEF("name", "Error",
                          LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE),
    LEPUS_PROP_STRING_DEF("message", "",
                          LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE),
};

/* Array */

static int JS_CopySubArray(LEPUSContext *ctx, LEPUSValueConst obj,
                           int64_t to_pos, int64_t from_pos, int64_t count,
                           int dir) {
  int64_t i, from, to, len = 0;
  LEPUSValue val = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  int fromPresent;
  LEPUSObject *p = nullptr;

  if (js_is_fast_array(ctx, obj)) {
    p = LEPUS_VALUE_GET_OBJ(obj);
  }

  /* XXX: should special case fast arrays */
  for (i = 0; i < count; i++) {
    if (dir < 0) {
      from = from_pos + count - i - 1;
      to = to_pos + count - i - 1;
    } else {
      from = from_pos + i;
      to = to_pos + i;
    }

    if (p && p->fast_array && from >= 0 && from < (len = p->u.array.count) &&
        to >= 0 && to < len) {
      int64_t l, j;
      l = count - i;
      /* Fast path for fast arrays. Since we don't look at the
         prototype chain, we can optimize only the cases where
         all the elements are present in the array. */
      if (dir < 0) {
        // Make sure index 'from - j' and 'to - j' is valid;
        l = min_int64(l, from + 1);
        l = min_int64(l, to + 1);

        for (j = 0; j < l; ++j) {
          set_value_gc(ctx, p->u.array.u.values + to - j,
                       p->u.array.u.values[from - j]);
        }
      } else {
        //  Make sure index 'from + j' and 'to + j' is valid;
        l = min_int64(l, len - from);
        l = min_int64(l, len - to);
        for (j = 0; j < l; ++j) {
          set_value_gc(ctx, p->u.array.u.values + to + j,
                       p->u.array.u.values[from + j]);
        }
      }
      i += l;
    } else {
      fromPresent = JS_TryGetPropertyInt64(ctx, obj, from, &val);
      if (fromPresent < 0) goto exception;

      if (fromPresent) {
        if (JS_SetPropertyInt64_GC(ctx, obj, to, val) < 0) goto exception;
      } else {
        if (JS_DeletePropertyInt64(ctx, obj, to, LEPUS_PROP_THROW) < 0)
          goto exception;
      }
    }
  }
  return 0;

exception:
  return -1;
}

static LEPUSValue js_array_constructor(LEPUSContext *ctx,
                                       LEPUSValueConst new_target, int argc,
                                       LEPUSValueConst *argv) {
  LEPUSValue obj;
  int i;

  obj = js_create_from_ctor_GC(ctx, new_target, JS_CLASS_ARRAY);
  if (LEPUS_IsException(obj)) return obj;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  if (argc == 1 && LEPUS_IsNumber(argv[0])) {
    uint32_t len;
    if (JS_ToArrayLengthFree(ctx, &len, argv[0], TRUE)) goto fail;
    if (JS_SetPropertyInternal_GC(ctx, obj, JS_ATOM_length,
                                  JS_NewUint32(ctx, len), LEPUS_PROP_THROW) < 0)
      goto fail;
  } else {
    for (i = 0; i < argc; i++) {
      if (JS_SetPropertyUint32_GC(ctx, obj, i, argv[i]) < 0) goto fail;
    }
  }
  return obj;
fail:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_array_from(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv) {
  // from(items, mapfn = void 0, this_arg = void 0)
  LEPUSValueConst items = argv[0], mapfn, this_arg;
  HandleScope func_scope(ctx, &items, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst args[2];
  func_scope.PushLEPUSValueArrayHandle(args, 2);
  LEPUSValue stack[2];
  func_scope.PushLEPUSValueArrayHandle(stack, 2);
  LEPUSValue iter = LEPUS_UNDEFINED, r, v = LEPUS_UNDEFINED,
             v2 = LEPUS_UNDEFINED, arrayLike;
  func_scope.PushHandle(&iter, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&v, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&v2, HANDLE_TYPE_LEPUS_VALUE);
  int64_t k, len;
  int done, mapping;

  mapping = FALSE;
  mapfn = LEPUS_UNDEFINED;
  func_scope.PushHandle(&mapfn, HANDLE_TYPE_LEPUS_VALUE);
  this_arg = LEPUS_UNDEFINED;
  func_scope.PushHandle(&this_arg, HANDLE_TYPE_LEPUS_VALUE);
  r = LEPUS_UNDEFINED;
  arrayLike = LEPUS_UNDEFINED;
  func_scope.PushHandle(&r, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&arrayLike, HANDLE_TYPE_LEPUS_VALUE);

  if (argc > 1) {
    mapfn = argv[1];
    if (!LEPUS_IsUndefined(mapfn)) {
      if (check_function(ctx, mapfn)) goto exception;
      mapping = 1;
      if (argc > 2) this_arg = argv[2];
    }
  }
  // <Primjs begin>
  items = JSRef2Value(ctx, items);
  // <Primjs end>
  iter =
      JS_GetPropertyInternal_GC(ctx, items, JS_ATOM_Symbol_iterator, items, 0);
  if (LEPUS_IsException(iter)) goto exception;
  if (!LEPUS_IsUndefined(iter)) {
    if (LEPUS_IsConstructor(ctx, this_val))
      r = JS_CallConstructor_GC(ctx, this_val, 0, NULL);
    else
      r = JS_NewArray_GC(ctx);
    if (LEPUS_IsException(r)) goto exception;
    stack[0] = items;
    if (js_for_of_start_gc(ctx, &stack[1], FALSE)) goto exception;
    for (k = 0;; k++) {
      v = JS_IteratorNext(ctx, stack[0], stack[1], 0, NULL, &done);
      if (LEPUS_IsException(v)) goto exception_close;
      if (done) break;
      if (mapping) {
        args[0] = v;
        args[1] = LEPUS_NewInt32(ctx, k);
        v2 = JS_Call_GC(ctx, mapfn, this_arg, 2, args);
        v = v2;
        if (LEPUS_IsException(v)) goto exception_close;
      }
      if (JS_DefinePropertyValueInt64_GC(
              ctx, r, k, v, LEPUS_PROP_C_W_E | LEPUS_PROP_THROW) < 0)
        goto exception_close;
    }
  } else {
    arrayLike = JS_ToObject_GC(ctx, items);
    if (LEPUS_IsException(arrayLike)) goto exception;
    if (js_get_length64(ctx, &len, arrayLike) < 0) goto exception;
    v = JS_NewInt64_GC(ctx, len);
    args[0] = v;
    if (LEPUS_IsConstructor(ctx, this_val)) {
      r = JS_CallConstructor_GC(ctx, this_val, 1, args);
    } else {
      r = js_array_constructor(ctx, LEPUS_UNDEFINED, 1, args);
    }
    if (LEPUS_IsException(r)) goto exception;
    for (k = 0; k < len; k++) {
      v = JS_GetPropertyInt64(ctx, arrayLike, k);
      if (LEPUS_IsException(v)) goto exception;
      if (mapping) {
        args[0] = v;
        args[1] = LEPUS_NewInt32(ctx, k);
        v2 = JS_Call_GC(ctx, mapfn, this_arg, 2, args);
        v = v2;
        if (LEPUS_IsException(v)) goto exception;
      }
      if (JS_DefinePropertyValueInt64_GC(
              ctx, r, k, v, LEPUS_PROP_C_W_E | LEPUS_PROP_THROW) < 0)
        goto exception;
    }
  }
  if (JS_SetPropertyInternal_GC(ctx, r, JS_ATOM_length, JS_NewUint32(ctx, k),
                                LEPUS_PROP_THROW) < 0)
    goto exception;
  goto done;

exception_close:
  if (!LEPUS_IsUndefined(stack[0])) JS_IteratorClose(ctx, stack[0], TRUE);
exception:
  r = LEPUS_EXCEPTION;
done:
  return r;
}

static LEPUSValue js_array_of(LEPUSContext *ctx, LEPUSValueConst this_val,
                              int argc, LEPUSValueConst *argv) {
  LEPUSValue obj, args[1];
  args[0] = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &args[0], HANDLE_TYPE_LEPUS_VALUE);
  int i;

  if (LEPUS_IsConstructor(ctx, this_val)) {
    args[0] = LEPUS_NewInt32(ctx, argc);
    obj = JS_CallConstructor_GC(ctx, this_val, 1,
                                reinterpret_cast<LEPUSValueConst *>(args));
  } else {
    obj = JS_NewArray_GC(ctx);
  }
  if (LEPUS_IsException(obj)) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  for (i = 0; i < argc; i++) {
    if (JS_CreateDataPropertyUint32(ctx, obj, i, argv[i], LEPUS_PROP_THROW) <
        0) {
      goto fail;
    }
  }
  if (JS_SetPropertyInternal_GC(ctx, obj, JS_ATOM_length,
                                JS_NewUint32(ctx, argc),
                                LEPUS_PROP_THROW) < 0) {
  fail:
    return LEPUS_EXCEPTION;
  }
  return obj;
}

static LEPUSValue js_array_isArray(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
  int ret;
  ret = JS_IsArray_GC(ctx, argv[0]);
  if (ret < 0) {
    return LEPUS_EXCEPTION;
  } else {
    if (ret == 0 && ctx && LEPUS_IsLepusRef(argv[0])) {
      ret = JS_LepusRefIsArray(ctx->rt, argv[0]);
    }
    return LEPUS_NewBool(ctx, ret);
  }
}

static LEPUSValue js_get_this(LEPUSContext *ctx, LEPUSValueConst this_val) {
  return this_val;
}

static LEPUSValue JS_ArraySpeciesCreate(LEPUSContext *ctx, LEPUSValueConst obj,
                                        LEPUSValueConst len_val) {
  LEPUSValue ctor, ret;
  int res;

  res = JS_IsArray_GC(ctx, obj);
  if (res < 0) return LEPUS_EXCEPTION;
  if (!res) return js_array_constructor(ctx, LEPUS_UNDEFINED, 1, &len_val);
  ctor = JS_SpeciesConstructor(ctx, obj, LEPUS_UNDEFINED);
  if (LEPUS_IsException(ctor)) return LEPUS_EXCEPTION;
  if (LEPUS_IsUndefined(ctor))
    return js_array_constructor(ctx, LEPUS_UNDEFINED, 1, &len_val);
  HandleScope func_scope(ctx, &ctor, HANDLE_TYPE_LEPUS_VALUE);
  ret = JS_CallConstructor_GC(ctx, ctor, 1, &len_val);
  return ret;
}

static const LEPUSCFunctionListEntry js_array_funcs[] = {
    LEPUS_CFUNC_DEF("isArray", 1, js_array_isArray),
    LEPUS_CFUNC_DEF("from", 1, js_array_from),
    LEPUS_CFUNC_DEF("of", 0, js_array_of),
    LEPUS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL),
};

LEPUSValue js_array_concat_gc(LEPUSContext *ctx, LEPUSValueConst this_val,
                              int argc, LEPUSValueConst *argv) {
  LEPUSValue obj, arr, val = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst e = LEPUS_UNDEFINED;
  func_scope.PushHandle(&e, HANDLE_TYPE_LEPUS_VALUE);
  int64_t len, k, n;
  int i, res;

  arr = LEPUS_UNDEFINED;
  obj = JS_ToObject_GC(ctx, this_val);
  if (LEPUS_IsException(obj)) goto exception;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);

  arr = JS_ArraySpeciesCreate(ctx, obj, LEPUS_NewInt32(ctx, 0));
  if (LEPUS_IsException(arr)) goto exception;
  func_scope.PushHandle(&arr, HANDLE_TYPE_LEPUS_VALUE);
  n = 0;
  for (i = -1; i < argc; i++) {
    if (i < 0) {
      e = obj;
    } else {
      e = argv[i];
    }

    res = JS_isConcatSpreadable(ctx, e);
    if (res < 0) goto exception;
    if (res) {
      if (js_get_length64(ctx, &len, e)) goto exception;
      if (n + len > MAX_SAFE_INTEGER) {
        LEPUS_ThrowTypeError(ctx, "Array loo long");
        goto exception;
      }
      for (k = 0; k < len; k++, n++) {
        res = JS_TryGetPropertyInt64(ctx, e, k, &val);
        if (res < 0) goto exception;
        if (res) {
          if (JS_DefinePropertyValueInt64_GC(
                  ctx, arr, n, val, LEPUS_PROP_C_W_E | LEPUS_PROP_THROW) < 0)
            goto exception;
        }
      }
    } else {
      if (n >= MAX_SAFE_INTEGER) {
        LEPUS_ThrowTypeError(ctx, "Array loo long");
        goto exception;
      }
      if (JS_DefinePropertyValueInt64_GC(
              ctx, arr, n, e, LEPUS_PROP_C_W_E | LEPUS_PROP_THROW) < 0)
        goto exception;
      n++;
    }
  }
  if (JS_SetPropertyInternal_GC(ctx, arr, JS_ATOM_length,
                                JS_NewInt64_GC(ctx, n), LEPUS_PROP_THROW) < 0)
    goto exception;

  return arr;

exception:
  return LEPUS_EXCEPTION;
}

#define special_every 0
#define special_some 1
#define special_forEach 2
#define special_map 3
#define special_filter 4
#define special_TA 8

static int js_typed_array_get_length_internal(LEPUSContext *ctx,
                                              LEPUSValueConst obj);

static LEPUSValue js_typed_array___speciesCreate(LEPUSContext *ctx,
                                                 LEPUSValueConst this_val,
                                                 int argc,
                                                 LEPUSValueConst *argv);

static LEPUSValue js_array_every(LEPUSContext *ctx, LEPUSValueConst this_val,
                                 int argc, LEPUSValueConst *argv, int special) {
  LEPUSValue obj = LEPUS_UNDEFINED, val, index_val = LEPUS_UNDEFINED,
             res = LEPUS_UNDEFINED, ret;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&index_val, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&res, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst args[3];
  func_scope.PushLEPUSValueArrayHandle(args, 3);
  LEPUSValueConst func, this_arg;
  int64_t len, k, n;
  int present;

  ret = LEPUS_UNDEFINED;
  val = LEPUS_UNDEFINED;
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  if (special & special_TA) {
    obj = this_val;
    len = js_typed_array_get_length_internal(ctx, obj);
    if (len < 0) goto exception;
  } else {
    obj = JS_ToObject_expect_lepusref(ctx, this_val);
    if (js_get_length64(ctx, &len, obj)) goto exception;
  }
  func = argv[0];
  this_arg = LEPUS_UNDEFINED;
  if (argc > 1) this_arg = argv[1];

  if (check_function(ctx, func)) goto exception;

  switch (special) {
    case special_every:
    case special_every | special_TA:
      ret = LEPUS_TRUE;
      break;
    case special_some:
    case special_some | special_TA:
      ret = LEPUS_FALSE;
      break;
    case special_map:
      /* XXX: JS_ArraySpeciesCreate should take int64_t */
      ret = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt64_GC(ctx, len));
      if (LEPUS_IsException(ret)) goto exception;
      break;
    case special_filter:
      ret = JS_ArraySpeciesCreate(ctx, obj, LEPUS_NewInt32(ctx, 0));
      if (LEPUS_IsException(ret)) goto exception;
      break;
    case special_map | special_TA:
      args[0] = obj;
      args[1] = LEPUS_NewInt32(ctx, len);
      ret = js_typed_array___speciesCreate(ctx, LEPUS_UNDEFINED, 2, args);
      if (LEPUS_IsException(ret)) goto exception;
      break;
    case special_filter | special_TA:
      ret = JS_NewArray_GC(ctx);
      if (LEPUS_IsException(ret)) goto exception;
      break;
  }
  n = 0;

  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  for (k = 0; k < len; k++) {
    if (special & special_TA) {
      val = JS_GetPropertyInt64(ctx, obj, k);
      if (LEPUS_IsException(val)) goto exception;
      present = TRUE;
    } else {
      present = JS_TryGetPropertyInt64(ctx, obj, k, &val);
      if (present < 0) goto exception;
    }
    if (present) {
      index_val = JS_NewInt64_GC(ctx, k);
      if (LEPUS_IsException(index_val)) goto exception;
      // <Primjs begin>
      args[0] = val;
      // <Primjs end>
      args[1] = index_val;
      args[2] = obj;
      res = JS_Call_GC(ctx, func, this_arg, 3, args);
      if (LEPUS_IsException(res)) goto exception;
      switch (special) {
        case special_every:
        case special_every | special_TA:
          if (!JS_ToBoolFree_GC(ctx, res)) {
            ret = LEPUS_FALSE;
            goto done;
          }
          break;
        case special_some:
        case special_some | special_TA:
          if (JS_ToBoolFree_GC(ctx, res)) {
            ret = LEPUS_TRUE;
            goto done;
          }
          break;
        case special_map:
          if (JS_DefinePropertyValueInt64_GC(
                  ctx, ret, k, res, LEPUS_PROP_C_W_E | LEPUS_PROP_THROW) < 0)
            goto exception;
          break;
        case special_map | special_TA:
          if (JS_SetPropertyValue_GC(ctx, ret, LEPUS_NewInt32(ctx, k), res,
                                     LEPUS_PROP_THROW) < 0)
            goto exception;
          break;
        case special_filter:
        case special_filter | special_TA:
          if (JS_ToBoolFree_GC(ctx, res)) {
            if (JS_DefinePropertyValueInt64_GC(
                    ctx, ret, n++, val, LEPUS_PROP_C_W_E | LEPUS_PROP_THROW) <
                0)
              goto exception;
          }
          break;
        default:
          break;
      }
      val = LEPUS_UNDEFINED;
    }
  }
done:
  if (special == (special_filter | special_TA)) {
    HandleScope block_scope(ctx);
    LEPUSValue arr;
    args[0] = obj;
    args[1] = LEPUS_NewInt32(ctx, n);
    arr = js_typed_array___speciesCreate(ctx, LEPUS_UNDEFINED, 2, args);
    if (LEPUS_IsException(arr)) goto exception;
    block_scope.PushHandle(&arr, HANDLE_TYPE_LEPUS_VALUE);
    args[0] = ret;
    res = JS_Invoke_GC(ctx, arr, JS_ATOM_set, 1, args);
    if (check_exception_free(ctx, res)) goto exception;
    ret = arr;
  }
  return ret;

exception:
  return LEPUS_EXCEPTION;
}

#define special_reduce 0
#define special_reduceRight 1

LEPUSValue js_array_reduce_gc(LEPUSContext *ctx, LEPUSValueConst this_val,
                              int argc, LEPUSValueConst *argv, int special) {
  LEPUSValue obj = LEPUS_UNDEFINED, val, index_val = LEPUS_UNDEFINED, acc,
             acc1 = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&index_val, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&acc1, HANDLE_TYPE_LEPUS_VALUE);

  LEPUSValueConst args[4];
  func_scope.PushLEPUSValueArrayHandle(args, 4);
  LEPUSValueConst func = LEPUS_UNDEFINED;
  func_scope.PushHandle(&func, HANDLE_TYPE_LEPUS_VALUE);
  int64_t len, k, k1;
  int present;

  acc = LEPUS_UNDEFINED;
  val = LEPUS_UNDEFINED;
  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&acc, HANDLE_TYPE_LEPUS_VALUE);
  if (special & special_TA) {
    obj = this_val;
    len = js_typed_array_get_length_internal(ctx, obj);
    if (len < 0) goto exception;
  } else {
    obj = JS_ToObject_GC(ctx, this_val);
    if (js_get_length64(ctx, &len, obj)) goto exception;
  }
  func = argv[0];

  if (check_function(ctx, func)) goto exception;

  k = 0;
  if (argc > 1) {
    acc = argv[1];
  } else {
    for (;;) {
      if (k >= len) {
        LEPUS_ThrowTypeError(ctx, "empty array");
        goto exception;
      }
      k1 = (special & special_reduceRight) ? len - k - 1 : k;
      k++;
      if (special & special_TA) {
        acc = JS_GetPropertyInt64(ctx, obj, k1);
        if (LEPUS_IsException(acc)) goto exception;
        break;
      } else {
        present = JS_TryGetPropertyInt64(ctx, obj, k1, &acc);
        if (present < 0) goto exception;
        if (present) break;
      }
    }
  }
  for (; k < len; k++) {
    k1 = (special & special_reduceRight) ? len - k - 1 : k;
    if (special & special_TA) {
      val = JS_GetPropertyInt64(ctx, obj, k1);
      if (LEPUS_IsException(val)) goto exception;
      present = TRUE;
    } else {
      present = JS_TryGetPropertyInt64(ctx, obj, k1, &val);
      if (present < 0) goto exception;
    }
    if (present) {
      index_val = JS_NewInt64_GC(ctx, k1);
      if (LEPUS_IsException(index_val)) goto exception;
      args[0] = acc;
      args[1] = val;
      args[2] = index_val;
      args[3] = obj;
      acc1 = JS_Call_GC(ctx, func, LEPUS_UNDEFINED, 4, args);
      val = LEPUS_UNDEFINED;
      if (LEPUS_IsException(acc1)) goto exception;
      acc = acc1;
    }
  }
  return acc;

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_array_fill(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv) {
  LEPUSValue obj;
  int64_t len, start, end;

  obj = JS_ToObject_GC(ctx, this_val);
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  if (js_get_length64(ctx, &len, obj)) goto exception;

  start = 0;
  if (argc > 1 && !LEPUS_IsUndefined(argv[1])) {
    if (JS_ToInt64Clamp(ctx, &start, argv[1], 0, len, len)) goto exception;
  }

  end = len;
  if (argc > 2 && !LEPUS_IsUndefined(argv[2])) {
    if (JS_ToInt64Clamp(ctx, &end, argv[2], 0, len, len)) goto exception;
  }

  /* XXX: should special case fast arrays */
  while (start < end) {
    if (JS_SetPropertyInt64_GC(ctx, obj, start, argv[0]) < 0) goto exception;
    start++;
  }
  return obj;

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_array_includes(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
  LEPUSValue obj, val = LEPUS_UNDEFINED;
  int64_t len, n, res;
  LEPUSValue *arrp = nullptr;
  uint32_t count;

  obj = JS_ToObject_GC(ctx, this_val);
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  if (js_get_length64(ctx, &len, obj)) goto exception;

  res = FALSE;
  if (len > 0) {
    n = 0;
    if (argc > 1) {
      if (JS_ToInt64Clamp(ctx, &n, argv[1], 0, len, len)) goto exception;
    }
    if (js_get_fast_array(ctx, obj, &arrp, &count)) {
      for (; n < count; n++) {
        if (js_strict_eq2(ctx, argv[0], arrp[n], JS_EQ_SAME_VALUE_ZERO)) {
          res = TRUE;
          goto done;
        }
      }
    }
    for (; n < len; n++) {
      val = JS_GetPropertyInt64(ctx, obj, n);
      if (LEPUS_IsException(val)) goto exception;
      if (js_strict_eq2(ctx, argv[0], val, JS_EQ_SAME_VALUE_ZERO)) {
        res = TRUE;
        break;
      }
    }
  }
done:
  return LEPUS_NewBool(ctx, res);

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_array_indexOf(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
  LEPUSValue obj, val = LEPUS_UNDEFINED;
  int64_t len, n, res;
  LEPUSValue *arrp = nullptr;
  uint32_t count;

  obj = JS_ToObject_GC(ctx, this_val);
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  if (js_get_length64(ctx, &len, obj)) goto exception;

  res = -1;
  if (len > 0) {
    n = 0;
    if (argc > 1) {
      if (JS_ToInt64Clamp(ctx, &n, argv[1], 0, len, len)) goto exception;
    }
    if (js_get_fast_array(ctx, obj, &arrp, &count)) {
      for (; n < count; n++) {
        if (js_strict_eq2(ctx, argv[0], arrp[n], JS_EQ_STRICT)) {
          res = n;
          goto done;
        }
      }
    }
    func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
    for (; n < len; n++) {
      int present = JS_TryGetPropertyInt64(ctx, obj, n, &val);
      if (present < 0) goto exception;
      if (present) {
        if (js_strict_eq2(ctx, argv[0], val, JS_EQ_STRICT)) {
          res = n;
          break;
        }
      }
    }
  }
done:
  return JS_NewInt64_GC(ctx, res);

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_array_lastIndexOf(LEPUSContext *ctx,
                                       LEPUSValueConst this_val, int argc,
                                       LEPUSValueConst *argv) {
  LEPUSValue obj, val = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  int64_t len, n, res;
  int present;

  obj = JS_ToObject_GC(ctx, this_val);
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  if (js_get_length64(ctx, &len, obj)) goto exception;

  res = -1;
  if (len > 0) {
    n = len - 1;
    if (argc > 1) {
      if (JS_ToInt64Clamp(ctx, &n, argv[1], -1, len - 1, len)) goto exception;
    }
    /* XXX: should special case fast arrays */
    for (; n >= 0; n--) {
      present = JS_TryGetPropertyInt64(ctx, obj, n, &val);
      if (present < 0) goto exception;
      if (present) {
        if (js_strict_eq2(ctx, argv[0], val, JS_EQ_STRICT)) {
          res = n;
          break;
        }
      }
    }
  }
  return JS_NewInt64_GC(ctx, res);

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_array_find(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv,
                                int findIndex) {
  LEPUSValueConst func = LEPUS_UNDEFINED, this_arg = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &func, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&this_arg, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst args[3];
  func_scope.PushLEPUSValueArrayHandle(args, 3);
  LEPUSValue obj, val, index_val, res = LEPUS_UNDEFINED;
  func_scope.PushHandle(&res, HANDLE_TYPE_LEPUS_VALUE);
  int64_t len, k;

  index_val = LEPUS_UNDEFINED;
  val = LEPUS_UNDEFINED;
  func_scope.PushHandle(&index_val, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  obj = JS_ToObject_GC(ctx, this_val);
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  if (js_get_length64(ctx, &len, obj)) goto exception;

  func = argv[0];
  if (check_function(ctx, func)) goto exception;

  this_arg = LEPUS_UNDEFINED;
  if (argc > 1) this_arg = argv[1];

  for (k = 0; k < len; k++) {
    index_val = JS_NewInt64_GC(ctx, k);
    if (LEPUS_IsException(index_val)) goto exception;
    val = JS_GetPropertyValue_GC(ctx, obj, index_val);
    if (LEPUS_IsException(val)) goto exception;
    args[0] = val;
    args[1] = index_val;
    args[2] = this_val;
    res = JS_Call_GC(ctx, func, this_arg, 3, args);
    if (LEPUS_IsException(res)) goto exception;
    if (JS_ToBoolFree_GC(ctx, res)) {
      if (findIndex) {
        return index_val;
      } else {
        return val;
      }
    }
  }
  if (findIndex)
    return LEPUS_NewInt32(ctx, -1);
  else
    return LEPUS_UNDEFINED;

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_array_toString(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
  LEPUSValue obj, method, ret;

  obj = JS_ToObject_GC(ctx, this_val);
  if (LEPUS_IsException(obj)) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  method = JS_GetPropertyInternal_GC(ctx, obj, JS_ATOM_join, obj, 0);
  if (LEPUS_IsException(method)) {
    ret = LEPUS_EXCEPTION;
  } else if (!LEPUS_IsFunction(ctx, method)) {
    /* Use intrinsic Object.prototype.toString */
    ret = js_object_toString(ctx, obj, 0, NULL);
  } else {
    ret = JS_CallFree_GC(ctx, method, obj, 0, NULL);
  }
  return ret;
}

static LEPUSValue js_array_join(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv,
                                int toLocaleString) {
  LEPUSValue obj, sep = LEPUS_UNDEFINED, el = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &sep, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&el, HANDLE_TYPE_LEPUS_VALUE);
  StringBuffer b_s, *b = &b_s;
  JSString *p = NULL;
  int64_t i, n;
  int c;

  obj = JS_ToObject_GC(ctx, this_val);
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  if (js_get_length64(ctx, &n, obj)) goto exception;

  c = ','; /* default separator */
  if (!toLocaleString && argc > 0 && !LEPUS_IsUndefined(argv[0])) {
    sep = JS_ToString_GC(ctx, argv[0]);
    if (LEPUS_IsException(sep)) goto exception;
    p = LEPUS_VALUE_GET_STRING(sep);
    if (p->len == 1 && !p->is_wide_char)
      c = p->u.str8[0];
    else
      c = -1;
  }
  string_buffer_init(ctx, b, 0);
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);

  for (i = 0; i < n; i++) {
    if (i > 0) {
      if (c >= 0) {
        string_buffer_putc8(b, c);
      } else {
        string_buffer_concat(b, p, 0, p->len);
      }
    }
    el = JS_GetPropertyUint32_GC(ctx, obj, i);
    if (LEPUS_IsException(el)) goto fail;
    if (!LEPUS_IsNull(el) && !LEPUS_IsUndefined(el)) {
      if (toLocaleString) {
        el = JS_ToLocaleStringFree(ctx, el);
      }
      if (string_buffer_concat_value_free(b, el)) goto fail;
    }
  }
  return string_buffer_end(b);

fail:
  b->str = NULL;
exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_array_pop(LEPUSContext *ctx, LEPUSValueConst this_val,
                               int argc, LEPUSValueConst *argv, int shift) {
  LEPUSValue obj, res = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &res, HANDLE_TYPE_LEPUS_VALUE);
  int64_t len, newLen;
  LEPUSValue *arrp = nullptr;
  func_scope.PushHandle(&arrp, HANDLE_TYPE_HEAP_OBJ);
  uint32_t count32;

  obj = JS_ToObject_GC(ctx, this_val);
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  if (js_get_length64(ctx, &len, obj)) goto exception;
  newLen = 0;
  if (len > 0) {
    newLen = len - 1;
    /* Special case fast arrays */
    if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) {
      LEPUSObject *p = LEPUS_VALUE_GET_OBJ(obj);
      if (shift) {
        res = arrp[0];
        memmove(arrp, arrp + 1, (count32 - 1) * sizeof(*arrp));
        p->u.array.count--;
      } else {
        res = arrp[count32 - 1];
        p->u.array.count--;
      }
    } else {
      if (shift) {
        res = JS_GetPropertyInt64(ctx, obj, 0);
        if (LEPUS_IsException(res)) goto exception;
        if (JS_CopySubArray(ctx, obj, 0, 1, len - 1, +1)) goto exception;
      } else {
        res = JS_GetPropertyInt64(ctx, obj, newLen);
        if (LEPUS_IsException(res)) goto exception;
      }
      if (JS_DeletePropertyInt64(ctx, obj, newLen, LEPUS_PROP_THROW) < 0)
        goto exception;
    }
  }
  if (JS_SetPropertyInternal_GC(ctx, obj, JS_ATOM_length,
                                JS_NewInt64_GC(ctx, newLen),
                                LEPUS_PROP_THROW) < 0)
    goto exception;

  return res;

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_array_push(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv, int unshift) {
  LEPUSValue obj;
  int i;
  int64_t len, from, newLen;

  obj = JS_ToObject_GC(ctx, this_val);
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  if (js_get_length64(ctx, &len, obj)) goto exception;
  newLen = len + argc;
  if (newLen > MAX_SAFE_INTEGER) {
    LEPUS_ThrowTypeError(ctx, "Array loo long");
    goto exception;
  }
  from = len;
  if (unshift && argc > 0) {
    if (JS_CopySubArray(ctx, obj, argc, 0, len, -1)) goto exception;
    from = 0;
  }
  for (i = 0; i < argc; i++) {
    if (JS_SetPropertyInt64_GC(ctx, obj, from + i, argv[i]) < 0) goto exception;
  }
  if (JS_SetPropertyInternal_GC(ctx, obj, JS_ATOM_length,
                                JS_NewInt64_GC(ctx, newLen),
                                LEPUS_PROP_THROW) < 0)
    goto exception;

  return JS_NewInt64_GC(ctx, newLen);

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_array_reverse(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
  LEPUSValue obj, lval, hval = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &hval, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue *arrp;
  int64_t len, l, h;
  int l_present, h_present;
  uint32_t count32;

  lval = LEPUS_UNDEFINED;
  func_scope.PushHandle(&lval, HANDLE_TYPE_LEPUS_VALUE);
  obj = JS_ToObject_GC(ctx, this_val);
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  if (js_get_length64(ctx, &len, obj)) goto exception;

  /* Special case fast arrays */
  if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) {
    uint32_t ll, hh;

    if (count32 > 1) {
      for (ll = 0, hh = count32 - 1; ll < hh; ll++, hh--) {
        lval = arrp[ll];
        arrp[ll] = arrp[hh];
        arrp[hh] = lval;
      }
    }
    return obj;
  }

  for (l = 0, h = len - 1; l < h; l++, h--) {
    l_present = JS_TryGetPropertyInt64(ctx, obj, l, &lval);
    if (l_present < 0) goto exception;
    h_present = JS_TryGetPropertyInt64(ctx, obj, h, &hval);
    if (h_present < 0) goto exception;
    if (h_present) {
      if (JS_SetPropertyInt64_GC(ctx, obj, l, hval) < 0) goto exception;

      if (l_present) {
        if (JS_SetPropertyInt64_GC(ctx, obj, h, lval) < 0) {
          lval = LEPUS_UNDEFINED;
          goto exception;
        }
        lval = LEPUS_UNDEFINED;
      } else {
        if (JS_DeletePropertyInt64(ctx, obj, h, LEPUS_PROP_THROW) < 0)
          goto exception;
      }
    } else {
      if (l_present) {
        if (JS_DeletePropertyInt64(ctx, obj, l, LEPUS_PROP_THROW) < 0)
          goto exception;
        if (JS_SetPropertyInt64_GC(ctx, obj, h, lval) < 0) {
          lval = LEPUS_UNDEFINED;
          goto exception;
        }
        lval = LEPUS_UNDEFINED;
      }
    }
  }
  return obj;

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_array_slice(LEPUSContext *ctx, LEPUSValueConst this_val,
                                 int argc, LEPUSValueConst *argv, int splice) {
  LEPUSValue obj, arr;
  LEPUSValue val = LEPUS_UNDEFINED, len_val = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&len_val, HANDLE_TYPE_LEPUS_VALUE);
  int64_t len, start, k, final, n, count, del_count, new_len;
  int kPresent;
  LEPUSValue *arrp = NULL;
  func_scope.PushHandle(&arrp, HANDLE_TYPE_HEAP_OBJ);
  uint32_t count32, i, item_count;

  arr = LEPUS_UNDEFINED;
  func_scope.PushHandle(&arr, HANDLE_TYPE_LEPUS_VALUE);
  obj = JS_ToObject_expect_lepusref(ctx, this_val);
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  if (js_get_length64(ctx, &len, obj)) goto exception;

  if (JS_ToInt64Clamp(ctx, &start, argv[0], 0, len, len)) goto exception;

  if (splice) {
    if (argc == 0) {
      item_count = 0;
      del_count = 0;
    } else if (argc == 1) {
      item_count = 0;
      del_count = len - start;
    } else {
      item_count = argc - 2;
      if (JS_ToInt64Clamp(ctx, &del_count, argv[1], 0, len - start, 0))
        goto exception;
    }
    if (len + item_count - del_count > MAX_SAFE_INTEGER) {
      LEPUS_ThrowTypeError(ctx, "Array loo long");
      goto exception;
    }
    count = del_count;
  } else {
    item_count = 0; /* avoid warning */
    final = len;
    if (!LEPUS_IsUndefined(argv[1])) {
      if (JS_ToInt64Clamp(ctx, &final, argv[1], 0, len, len)) goto exception;
    }
    count = max_int64(final - start, 0);
  }

#ifdef ENABLE_LEPUSNG
  if (LEPUS_IsLepusRef(obj)) {
    arr = ctx->rt->primjs_callbacks_.jsarray_slice(
        ctx, obj, start, count, item_count, argv + 2, splice);
    return arr;
  }
#endif

  len_val = JS_NewInt64_GC(ctx, count);
  arr = JS_ArraySpeciesCreate(ctx, obj, len_val);
  if (LEPUS_IsException(arr)) goto exception;

  k = start;
  final = start + count;
  n = 0;
  /* The fast array test on arr ensures that
     JS_CreateDataPropertyUint32() won't modify obj in case arr is
     an exotic object */
  /* Special case fast arrays */
  if (js_get_fast_array(ctx, obj, &arrp, &count32) &&
      js_is_fast_array(ctx, arr)) {
    /* XXX: should share code with fast array constructor */
    for (; k < final && k < count32; k++, n++) {
      if (JS_CreateDataPropertyUint32(ctx, arr, n, arrp[k], LEPUS_PROP_THROW) <
          0)
        goto exception;
    }
  }
  /* Copy the remaining elements if any (handle case of inherited properties)
   */
  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  for (; k < final; k++, n++) {
    kPresent = JS_TryGetPropertyInt64(ctx, obj, k, &val);
    if (kPresent < 0) goto exception;
    if (kPresent) {
      if (JS_CreateDataPropertyUint32(ctx, arr, n, val, LEPUS_PROP_THROW) < 0)
        goto exception;
    }
  }
  if (JS_SetPropertyInternal_GC(ctx, arr, JS_ATOM_length,
                                JS_NewInt64_GC(ctx, n), LEPUS_PROP_THROW) < 0)
    goto exception;

  if (splice) {
    new_len = len + item_count - del_count;
    if (item_count != del_count) {
      if (JS_CopySubArray(ctx, obj, start + item_count, start + del_count,
                          len - (start + del_count),
                          item_count <= del_count ? +1 : -1) < 0)
        goto exception;

      for (k = len; k-- > new_len;) {
        if (JS_DeletePropertyInt64(ctx, obj, k, LEPUS_PROP_THROW) < 0)
          goto exception;
      }
    }
    for (i = 0; i < item_count; i++) {
      int j = i;
      if (JS_SetPropertyInt64_GC(ctx, obj, start + i, argv[j + 2]) < 0)
        goto exception;
    }
    if (JS_SetPropertyInternal_GC(ctx, obj, JS_ATOM_length,
                                  JS_NewInt64_GC(ctx, new_len),
                                  LEPUS_PROP_THROW) < 0)
      goto exception;
  }
  return arr;

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_array_copyWithin(LEPUSContext *ctx,
                                      LEPUSValueConst this_val, int argc,
                                      LEPUSValueConst *argv) {
  LEPUSValue obj;
  int64_t len, from, to, final, count;

  obj = JS_ToObject_GC(ctx, this_val);
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  if (js_get_length64(ctx, &len, obj)) goto exception;

  if (JS_ToInt64Clamp(ctx, &to, argv[0], 0, len, len)) goto exception;

  if (JS_ToInt64Clamp(ctx, &from, argv[1], 0, len, len)) goto exception;

  final = len;
  if (argc > 2 && !LEPUS_IsUndefined(argv[2])) {
    if (JS_ToInt64Clamp(ctx, &final, argv[2], 0, len, len)) goto exception;
  }

  count = min_int64(final - from, len - to);

  if (JS_CopySubArray(ctx, obj, to, from, count,
                      (from < to && to < from + count) ? -1 : +1))
    goto exception;

  return obj;

exception:
  return LEPUS_EXCEPTION;
}

static int64_t JS_FlattenIntoArray(LEPUSContext *ctx, LEPUSValueConst target,
                                   LEPUSValueConst source, int64_t sourceLen,
                                   int64_t targetIndex, int depth,
                                   LEPUSValueConst mapperFunction,
                                   LEPUSValueConst thisArg) {
  LEPUSValue element = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &element, HANDLE_TYPE_LEPUS_VALUE);
  int64_t sourceIndex, elementLen;
  int present, is_array;

  for (sourceIndex = 0; sourceIndex < sourceLen; sourceIndex++) {
    present = JS_TryGetPropertyInt64(ctx, source, sourceIndex, &element);
    if (present < 0) return -1;
    if (!present) continue;
    if (!LEPUS_IsUndefined(mapperFunction)) {
      LEPUSValueConst args[3] = {element, JS_NewInt64_GC(ctx, sourceIndex),
                                 source};
      element = JS_Call_GC(ctx, mapperFunction, thisArg, 3, args);
      if (LEPUS_IsException(element)) return -1;
    }
    if (depth > 0) {
      is_array = JS_IsArray_GC(ctx, element);
      if (is_array < 0) goto fail;
      if (is_array) {
        if (js_get_length64(ctx, &elementLen, element) < 0) goto fail;
        targetIndex =
            JS_FlattenIntoArray(ctx, target, element, elementLen, targetIndex,
                                depth - 1, LEPUS_UNDEFINED, LEPUS_UNDEFINED);
        if (targetIndex < 0) goto fail;
        continue;
      }
    }
    if (targetIndex >= MAX_SAFE_INTEGER) {
      LEPUS_ThrowTypeError(ctx, "Array too long");
      goto fail;
    }
    if (JS_DefinePropertyValueInt64_GC(ctx, target, targetIndex, element,
                                       LEPUS_PROP_C_W_E | LEPUS_PROP_THROW) < 0)
      return -1;
    targetIndex++;
  }
  return targetIndex;

fail:
  return -1;
}

static LEPUSValue js_array_flatten(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv, int map) {
  LEPUSValue obj, arr;
  LEPUSValueConst mapperFunction = LEPUS_UNDEFINED, thisArg = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &mapperFunction, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&thisArg, HANDLE_TYPE_LEPUS_VALUE);
  int64_t sourceLen;
  int depthNum;

  arr = LEPUS_UNDEFINED;
  func_scope.PushHandle(&arr, HANDLE_TYPE_LEPUS_VALUE);
  obj = JS_ToObject_GC(ctx, this_val);
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  if (js_get_length64(ctx, &sourceLen, obj)) goto exception;

  depthNum = 1;
  mapperFunction = LEPUS_UNDEFINED;
  thisArg = LEPUS_UNDEFINED;
  if (map) {
    mapperFunction = argv[0];
    if (argc > 1) {
      thisArg = argv[1];
    }
    if (check_function(ctx, mapperFunction)) goto exception;
  } else {
    if (argc > 0 && !LEPUS_IsUndefined(argv[0])) {
      if (JS_ToInt32Sat(ctx, &depthNum, argv[0]) < 0) goto exception;
    }
  }
  arr = JS_ArraySpeciesCreate(ctx, obj, LEPUS_NewInt32(ctx, 0));
  if (LEPUS_IsException(arr)) goto exception;
  if (JS_FlattenIntoArray(ctx, arr, obj, sourceLen, 0, depthNum, mapperFunction,
                          thisArg) < 0)
    goto exception;
  return arr;

exception:
  return LEPUS_EXCEPTION;
}

/* Array sort */

static int js_array_cmp_generic(const void *a, const void *b, void *opaque) {
  struct array_sort_context *psc =
      static_cast<struct array_sort_context *>(opaque);
  LEPUSContext *ctx = psc->ctx;
  HandleScope func_scope(ctx);
  LEPUSValueConst argv[2];
  func_scope.PushLEPUSValueArrayHandle(argv, 2);
  LEPUSValue res = LEPUS_UNDEFINED;
  func_scope.PushHandle(&res, HANDLE_TYPE_LEPUS_VALUE);
  ValueSlot *ap = reinterpret_cast<ValueSlot *>(const_cast<void *>(a));
  ValueSlot *bp = reinterpret_cast<ValueSlot *>(const_cast<void *>(b));
  int cmp;

  if (psc->exception) return 0;

  if (psc->has_method) {
    /* custom sort function is specified as returning 0 for identical
     * objects: avoid method call overhead.
     */
    if (!memcmp(&ap->val, &bp->val, sizeof(ap->val))) goto cmp_same;
    argv[0] = ap->val;
    argv[1] = bp->val;
    res = JS_Call_GC(ctx, psc->method, LEPUS_UNDEFINED, 2, argv);
    if (LEPUS_IsException(res)) goto exception;
    if (LEPUS_VALUE_IS_INT(res)) {
      int val = LEPUS_VALUE_GET_INT(res);
      cmp = (val > 0) - (val < 0);
    } else {
      double val;
      if (JS_ToFloat64Free(ctx, &val, res) < 0) goto exception;
      cmp = (val > 0) - (val < 0);
    }
  } else {
    /* Not supposed to bypass ToString even for identical objects as
     * tested in test262/test/built-ins/Array/prototype/sort/bug_596_1.lepus
     */
    if (!ap->str) {
      LEPUSValue str = JS_ToString_GC(ctx, ap->val);
      if (LEPUS_IsException(str)) goto exception;
      ap->str = LEPUS_VALUE_GET_STRING(str);
    }
    if (!bp->str) {
      LEPUSValue str = JS_ToString_GC(ctx, bp->val);
      if (LEPUS_IsException(str)) goto exception;
      bp->str = LEPUS_VALUE_GET_STRING(str);
    }
    cmp = js_string_compare(ctx, ap->str, bp->str);
  }
  if (cmp != 0) return cmp;
cmp_same:
  /* make sort stable: compare array offsets */
  return (ap->pos > bp->pos) - (ap->pos < bp->pos);

exception:
  psc->exception = 1;
  return 0;
}

static LEPUSValue js_array_sort(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv) {
  struct array_sort_context asc = {ctx, 0, 0, argv[0]};
  LEPUSValue obj = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  ValueSlot *array = NULL;
  func_scope.PushHandle(&array, HANDLE_TYPE_HEAP_OBJ);
  size_t array_size = 0, pos = 0, n = 0;
  int64_t i, len, undefined_count = 0;
  int present;

  if (!LEPUS_IsUndefined(asc.method)) {
    if (check_function(ctx, asc.method)) goto exception;
    asc.has_method = 1;
  }
  obj = JS_ToObject_GC(ctx, this_val);
  if (js_get_length64(ctx, &len, obj)) goto exception;

  /* XXX: should special case fast arrays */
  for (i = 0; i < len; i++) {
    if (pos >= array_size) {
      size_t new_size, slack;
      ValueSlot *new_array;
      new_size = (array_size + (array_size >> 1) + 31) & ~15;
      new_array = static_cast<ValueSlot *>(lepus_realloc2(
          ctx, array, new_size * sizeof(*array), &slack, ALLOC_TAG_ValueSlot));
      if (new_array == NULL) goto exception;
      new_size += slack / sizeof(*new_array);
      array = new_array;
      array_size = new_size;
    }
    present = JS_TryGetPropertyInt64(ctx, obj, i, &array[pos].val);
    if (present < 0) goto exception;
    if (present == 0) continue;
    if (LEPUS_IsUndefined(array[pos].val)) {
      undefined_count++;
      continue;
    }
    array[pos].str = NULL;
    array[pos].pos = i;
    pos++;
    set_heap_obj_len(array, pos);
  }
  rqsort(array, pos, sizeof(*array), js_array_cmp_generic, &asc);
  if (asc.exception) goto exception;

  /* XXX: should special case fast arrays */
  while (n < pos) {
    // if (array[n].str)
    if (array[n].pos != n) {
      if (JS_SetPropertyInt64_GC(ctx, obj, n, array[n].val) < 0) {
        n++;
        goto exception;
      }
    }
    n++;
  }
  for (i = n; undefined_count-- > 0; i++) {
    if (JS_SetPropertyInt64_GC(ctx, obj, i, LEPUS_UNDEFINED) < 0) goto fail;
  }
  for (; i < len; i++) {
    if (JS_DeletePropertyInt64(ctx, obj, i, LEPUS_PROP_THROW) < 0) goto fail;
  }
  return obj;

exception:
fail:
  return LEPUS_EXCEPTION;
}

typedef struct JSArrayIteratorData {
  LEPUSValue obj;
  JSIteratorKindEnum kind;
  uint32_t idx;
} JSArrayIteratorData;

static LEPUSValue js_create_array(LEPUSContext *ctx, int len,
                                  LEPUSValueConst *tab) {
  LEPUSValue obj;
  int i;

  obj = JS_NewArray_GC(ctx);
  if (LEPUS_IsException(obj)) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  for (i = 0; i < len; i++) {
    if (JS_CreateDataPropertyUint32(ctx, obj, i, tab[i], 0) < 0) {
      return LEPUS_EXCEPTION;
    }
  }
  return obj;
}

static LEPUSValue js_create_array_iterator(LEPUSContext *ctx,
                                           LEPUSValueConst this_val, int argc,
                                           LEPUSValueConst *argv, int magic) {
  LEPUSValue enum_obj, arr;
  JSArrayIteratorData *it;
  JSIteratorKindEnum kind;
  int class_id;

  kind = static_cast<JSIteratorKindEnum>(magic & 3);
  if (magic & 4) {
    /* string iterator case */
    arr = JS_ToStringCheckObject(ctx, this_val);
    class_id = JS_CLASS_STRING_ITERATOR;
  } else {
    arr = JS_ToObject_GC(ctx, this_val);
    class_id = JS_CLASS_ARRAY_ITERATOR;
  }
  HandleScope func_scope(ctx, &arr, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_IsException(arr)) goto fail;
  enum_obj = JS_NewObjectClass_GC(ctx, class_id);
  if (LEPUS_IsException(enum_obj)) goto fail;
  func_scope.PushHandle(&enum_obj, HANDLE_TYPE_LEPUS_VALUE);
  it = static_cast<JSArrayIteratorData *>(
      lepus_malloc(ctx, sizeof(*it), ALLOC_TAG_JSArrayIteratorData));
  if (!it) goto fail1;
  it->obj = arr;
  it->kind = kind;
  it->idx = 0;
  LEPUS_SetOpaque(enum_obj, it);
  return enum_obj;
fail1:
fail:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_array_iterator_next(LEPUSContext *ctx,
                                         LEPUSValueConst this_val, int argc,
                                         LEPUSValueConst *argv, BOOL *pdone,
                                         int magic) {
  JSArrayIteratorData *it;
  uint32_t len, idx;
  LEPUSValue val = LEPUS_UNDEFINED, obj = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSObject *p;

  it = static_cast<JSArrayIteratorData *>(
      LEPUS_GetOpaque2(ctx, this_val, JS_CLASS_ARRAY_ITERATOR));
  if (!it) goto fail1;
  if (LEPUS_IsUndefined(it->obj)) goto done;
  p = LEPUS_VALUE_GET_OBJ(it->obj);
  if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
      p->class_id <= JS_CLASS_BIG_UINT64_ARRAY) {
    if (typed_array_is_detached(ctx, p)) {
      JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
      goto fail1;
    }
    len = p->u.array.count;
  } else {
    if (js_get_length32_gc(ctx, &len, it->obj)) {
    fail1:
      *pdone = FALSE;
      return LEPUS_EXCEPTION;
    }
  }
  idx = it->idx;
  if (idx >= len) {
    it->obj = LEPUS_UNDEFINED;
  done:
    *pdone = TRUE;
    return LEPUS_UNDEFINED;
  }
  it->idx = idx + 1;
  *pdone = FALSE;
  if (it->kind == JS_ITERATOR_KIND_KEY) {
    return JS_NewUint32(ctx, idx);
  } else {
    val = JS_GetPropertyUint32_GC(ctx, it->obj, idx);
    if (LEPUS_IsException(val)) return LEPUS_EXCEPTION;
    if (it->kind == JS_ITERATOR_KIND_VALUE) {
      return val;
    } else {
      HandleScope block_scope(ctx->rt);
      LEPUSValueConst args[2];
      LEPUSValue num;
      num = JS_NewUint32(ctx, idx);
      args[0] = num;
      args[1] = val;
      block_scope.PushHandle(&args[0], HANDLE_TYPE_LEPUS_VALUE);
      block_scope.PushHandle(&args[1], HANDLE_TYPE_LEPUS_VALUE);
      obj = js_create_array(ctx, 2, args);
      return obj;
    }
  }
}

static LEPUSValue js_iterator_proto_iterator(LEPUSContext *ctx,
                                             LEPUSValueConst this_val, int argc,
                                             LEPUSValueConst *argv) {
  return this_val;
}

static const LEPUSCFunctionListEntry js_iterator_proto_funcs[] = {
    LEPUS_CFUNC_DEF("[Symbol.iterator]", 0, js_iterator_proto_iterator),
};

static const LEPUSCFunctionListEntry js_array_unscopables_funcs[] = {
    LEPUS_PROP_BOOL_DEF("at", TRUE, LEPUS_PROP_C_W_E),
    LEPUS_PROP_BOOL_DEF("copyWithin", TRUE, LEPUS_PROP_C_W_E),
    LEPUS_PROP_BOOL_DEF("entries", TRUE, LEPUS_PROP_C_W_E),
    LEPUS_PROP_BOOL_DEF("fill", TRUE, LEPUS_PROP_C_W_E),
    LEPUS_PROP_BOOL_DEF("find", TRUE, LEPUS_PROP_C_W_E),
    LEPUS_PROP_BOOL_DEF("findIndex", TRUE, LEPUS_PROP_C_W_E),
    LEPUS_PROP_BOOL_DEF("findLast", TRUE, LEPUS_PROP_C_W_E),
    LEPUS_PROP_BOOL_DEF("findLastIndex", TRUE, LEPUS_PROP_C_W_E),
    LEPUS_PROP_BOOL_DEF("flat", TRUE, LEPUS_PROP_C_W_E),
    LEPUS_PROP_BOOL_DEF("flatMap", TRUE, LEPUS_PROP_C_W_E),
    LEPUS_PROP_BOOL_DEF("includes", TRUE, LEPUS_PROP_C_W_E),
    LEPUS_PROP_BOOL_DEF("keys", TRUE, LEPUS_PROP_C_W_E),
    LEPUS_PROP_BOOL_DEF("toReversed", TRUE, LEPUS_PROP_C_W_E),
    LEPUS_PROP_BOOL_DEF("toSorted", TRUE, LEPUS_PROP_C_W_E),
    LEPUS_PROP_BOOL_DEF("toSpliced", TRUE, LEPUS_PROP_C_W_E),
    LEPUS_PROP_BOOL_DEF("values", TRUE, LEPUS_PROP_C_W_E),
};

static const LEPUSCFunctionListEntry js_array_proto_funcs[] = {
    LEPUS_CFUNC_DEF("concat", 1, js_array_concat_gc),
    LEPUS_CFUNC_MAGIC_DEF("every", 1, js_array_every, special_every),
    LEPUS_CFUNC_MAGIC_DEF("some", 1, js_array_every, special_some),
    LEPUS_CFUNC_MAGIC_DEF("forEach", 1, js_array_every, special_forEach),
    LEPUS_CFUNC_MAGIC_DEF("map", 1, js_array_every, special_map),
    LEPUS_CFUNC_MAGIC_DEF("filter", 1, js_array_every, special_filter),
    LEPUS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce_gc, special_reduce),
    LEPUS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce_gc,
                          special_reduceRight),
    LEPUS_CFUNC_DEF("fill", 1, js_array_fill),
    LEPUS_CFUNC_MAGIC_DEF("find", 1, js_array_find, 0),
    LEPUS_CFUNC_MAGIC_DEF("findIndex", 1, js_array_find, 1),
    LEPUS_CFUNC_DEF("indexOf", 1, js_array_indexOf),
    LEPUS_CFUNC_DEF("lastIndexOf", 1, js_array_lastIndexOf),
    LEPUS_CFUNC_DEF("includes", 1, js_array_includes),
    LEPUS_CFUNC_MAGIC_DEF("join", 1, js_array_join, 0),
    LEPUS_CFUNC_DEF("toString", 0, js_array_toString),
    LEPUS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_array_join, 1),
    LEPUS_CFUNC_MAGIC_DEF("pop", 0, js_array_pop, 0),
    LEPUS_CFUNC_MAGIC_DEF("push", 1, js_array_push, 0),
    LEPUS_CFUNC_MAGIC_DEF("shift", 0, js_array_pop, 1),
    LEPUS_CFUNC_MAGIC_DEF("unshift", 1, js_array_push, 1),
    LEPUS_CFUNC_DEF("reverse", 0, js_array_reverse),
    LEPUS_CFUNC_DEF("sort", 1, js_array_sort),
    LEPUS_CFUNC_MAGIC_DEF("slice", 2, js_array_slice, 0),
    LEPUS_CFUNC_MAGIC_DEF("splice", 2, js_array_slice, 1),
    LEPUS_CFUNC_DEF("copyWithin", 2, js_array_copyWithin),
    LEPUS_CFUNC_MAGIC_DEF("flatMap", 1, js_array_flatten, 1),
    LEPUS_CFUNC_MAGIC_DEF("flat", 0, js_array_flatten, 0),
    LEPUS_CFUNC_MAGIC_DEF("flatten", 0, js_array_flatten, 0),
    LEPUS_CFUNC_MAGIC_DEF("values", 0, js_create_array_iterator,
                          JS_ITERATOR_KIND_VALUE),
    LEPUS_ALIAS_DEF("[Symbol.iterator]", "values"),
    LEPUS_CFUNC_MAGIC_DEF("keys", 0, js_create_array_iterator,
                          JS_ITERATOR_KIND_KEY),
    LEPUS_CFUNC_MAGIC_DEF("entries", 0, js_create_array_iterator,
                          JS_ITERATOR_KIND_KEY_AND_VALUE),
    LEPUS_OBJECT_DEF("[Symbol.unscopables]", js_array_unscopables_funcs,
                     countof(js_array_unscopables_funcs),
                     LEPUS_PROP_CONFIGURABLE),
};

static const LEPUSCFunctionListEntry js_array_iterator_proto_funcs[] = {
    LEPUS_ITERATOR_NEXT_DEF("next", 0, js_array_iterator_next, 0),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "Array Iterator",
                          LEPUS_PROP_CONFIGURABLE),
};

/* Number */

static LEPUSValue js_number_constructor(LEPUSContext *ctx,
                                        LEPUSValueConst new_target, int argc,
                                        LEPUSValueConst *argv) {
  LEPUSValue val = LEPUS_UNDEFINED, obj = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  if (argc == 0) {
    val = LEPUS_NewInt32(ctx, 0);
  } else {
    val = JS_ToNumeric(ctx, argv[0]);
    if (LEPUS_IsException(val)) return val;
    switch (LEPUS_VALUE_GET_TAG(val)) {
      case LEPUS_TAG_BIG_INT: {
        JSBigInt *p = LEPUS_VALUE_GET_BIGINT(val);
        double d;
        d = js_bigint_to_float64(ctx, p);
        val = LEPUS_NewFloat64(ctx, d);
      } break;
      default:
        break;
    }
  }
  if (!LEPUS_IsUndefined(new_target)) {
    obj = js_create_from_ctor_GC(ctx, new_target, JS_CLASS_NUMBER);
    if (!LEPUS_IsException(obj)) JS_SetObjectData(ctx, obj, val);
    return obj;
  }
  return val;
}

#if 0
static LEPUSValue js_number___toInteger(LEPUSContext *ctx, LEPUSValueConst this_val,
                                     int argc, LEPUSValueConst *argv) {
    return JS_ToIntegerFree(ctx, argv[0]);
}

static LEPUSValue js_number___toLength(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
  int64_t v;
  if (JS_ToLengthFree(ctx, &v,argv[0])) {
      return LEPUS_EXCEPTION;
  }
  return JS_NewInt64_GC(ctx, v);
}
#endif

static LEPUSValue js_number_isNaN(LEPUSContext *ctx, LEPUSValueConst this_val,
                                  int argc, LEPUSValueConst *argv) {
  /* XXX: should just check for float and big_float */
  if (!LEPUS_IsNumber(argv[0])) return LEPUS_FALSE;
  return js_global_isNaN(ctx, this_val, argc, argv);
}

static LEPUSValue js_number_isFinite(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv) {
  if (!LEPUS_IsNumber(argv[0])) return LEPUS_FALSE;
  return js_global_isFinite(ctx, this_val, argc, argv);
}

static LEPUSValue js_number_isInteger(LEPUSContext *ctx,
                                      LEPUSValueConst this_val, int argc,
                                      LEPUSValueConst *argv) {
  int ret;
  ret = JS_NumberIsInteger(ctx, argv[0]);
  if (ret < 0)
    return LEPUS_EXCEPTION;
  else
    return LEPUS_NewBool(ctx, ret);
}

static LEPUSValue js_number_isSafeInteger(LEPUSContext *ctx,
                                          LEPUSValueConst this_val, int argc,
                                          LEPUSValueConst *argv) {
  double d;
  if (!LEPUS_IsNumber(argv[0])) return LEPUS_FALSE;
  if (unlikely(JS_ToFloat64_GC(ctx, &d, argv[0]))) return LEPUS_EXCEPTION;
  return LEPUS_NewBool(ctx,
                       isfinite(d) && floor(d) == d &&
                           fabs(d) <= static_cast<double>(MAX_SAFE_INTEGER));
}

static LEPUSCFunctionListEntry js_number_funcs[] = {
    /* global ParseInt and parseFloat should be defined already or delayed */
    LEPUS_ALIAS_BASE_DEF("parseInt", "parseInt", 0),
    LEPUS_ALIAS_BASE_DEF("parseFloat", "parseFloat", 0),
    LEPUS_CFUNC_DEF("isNaN", 1, js_number_isNaN),
    LEPUS_CFUNC_DEF("isFinite", 1, js_number_isFinite),
    LEPUS_CFUNC_DEF("isInteger", 1, js_number_isInteger),
    LEPUS_CFUNC_DEF("isSafeInteger", 1, js_number_isSafeInteger),
    LEPUS_PROP_DOUBLE_DEF("MAX_VALUE", 1.7976931348623157e+308, 0),
    LEPUS_PROP_DOUBLE_DEF("MIN_VALUE", 5e-324, 0),
    LEPUS_PROP_DOUBLE_DEF("NaN", LEPUS_FLOAT64_NAN, 0),
    LEPUS_PROP_DOUBLE_DEF("NEGATIVE_INFINITY", -INFINITY, 0),
    LEPUS_PROP_DOUBLE_DEF("POSITIVE_INFINITY", INFINITY, 0),
    LEPUS_PROP_DOUBLE_DEF("EPSILON", 2.220446049250313e-16, 0),        /* ES6 */
    LEPUS_PROP_DOUBLE_DEF("MAX_SAFE_INTEGER", 9007199254740991.0, 0),  /* ES6 */
    LEPUS_PROP_DOUBLE_DEF("MIN_SAFE_INTEGER", -9007199254740991.0, 0), /* ES6 */
    // LEPUS_CFUNC_DEF("__toInteger", 1, js_number___toInteger ),
    // LEPUS_CFUNC_DEF("__toLength", 1, js_number___toLength ),
};

static LEPUSValue js_thisNumberValue(LEPUSContext *ctx,
                                     LEPUSValueConst this_val) {
  if (LEPUS_IsNumber(this_val)) return this_val;

  if (LEPUS_VALUE_IS_OBJECT(this_val)) {
    LEPUSObject *p = LEPUS_VALUE_GET_OBJ(this_val);
    if (p->class_id == JS_CLASS_NUMBER) {
      if (LEPUS_IsNumber(p->u.object_data)) return p->u.object_data;
    }
  }
  return LEPUS_ThrowTypeError(ctx, "not a number");
}

static LEPUSValue js_number_valueOf(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
  return js_thisNumberValue(ctx, this_val);
}

static int32_t js_get_radix(LEPUSContext *ctx, LEPUSValueConst val) {
  int32_t radix;
  if (JS_ToInt32Sat(ctx, &radix, val)) return -1;
  if (radix < 2 || radix > 36) {
    LEPUS_ThrowRangeError(ctx, "radix must be between 2 and 36");
    return -1;
  }
  return radix;
}

static LEPUSValue js_number_toString(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv, int magic) {
  LEPUSValue val;
  int base, flags;
  double d;

  val = js_thisNumberValue(ctx, this_val);
  if (LEPUS_IsException(val)) return val;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  if (magic || LEPUS_IsUndefined(argv[0])) {
    base = 10;
  } else {
    base = js_get_radix(ctx, argv[0]);
    if (base < 0) goto fail;
  }
  if (LEPUS_VALUE_GET_TAG(val) == LEPUS_TAG_INT) {
    char buf1[70];
    int32_t len;
    len = i64toa_radix(buf1, LEPUS_VALUE_GET_INT(val), base);
    return js_new_string8(ctx, buf1, len);
  }
  if (JS_ToFloat64Free(ctx, &d, val)) return LEPUS_EXCEPTION;
  flags = JS_DTOA_FORMAT_FREE;
  if (base != 10) flags |= JS_DTOA_EXP_DISABLED;
  return js_dtoa2(ctx, d, base, 0, flags);
fail:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_number_toFixed(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
  LEPUSValue val;
  int f, flags;
  double d;

  val = js_thisNumberValue(ctx, this_val);
  if (LEPUS_IsException(val)) return val;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  if (JS_ToFloat64Free(ctx, &d, val)) return LEPUS_EXCEPTION;
  if (JS_ToInt32Sat(ctx, &f, argv[0])) return LEPUS_EXCEPTION;
  if (f < 0 || f > 100)
    return LEPUS_ThrowRangeError(ctx, "invalid number of digits");
  if (fabs(d) >= 1e21) {
    flags = JS_DTOA_FORMAT_FREE;
  } else {
    flags = JS_DTOA_FORMAT_FRAC;
  }
  return js_dtoa2(ctx, d, 10, f, flags);
}

static LEPUSValue js_number_toExponential(LEPUSContext *ctx,
                                          LEPUSValueConst this_val, int argc,
                                          LEPUSValueConst *argv) {
  LEPUSValue val;
  int f, flags;
  double d;

  val = js_thisNumberValue(ctx, this_val);
  if (LEPUS_IsException(val)) return val;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  if (JS_ToFloat64Free(ctx, &d, val)) return LEPUS_EXCEPTION;
  if (JS_ToInt32Sat(ctx, &f, argv[0])) return LEPUS_EXCEPTION;
  if (!isfinite(d)) {
    return JS_ToStringFree(ctx, __JS_NewFloat64(ctx, d));
  }
  if (LEPUS_IsUndefined(argv[0])) {
    flags = 0;
    f = 0;
  } else {
    if (f < 0 || f > 100)
      return LEPUS_ThrowRangeError(ctx, "invalid number of digits");
    f++;
    flags = JS_DTOA_FORMAT_FIXED;
  }
  return js_dtoa2(ctx, d, 10, f, flags | JS_DTOA_EXP_ENABLED);
}

static LEPUSValue js_number_toPrecision(LEPUSContext *ctx,
                                        LEPUSValueConst this_val, int argc,
                                        LEPUSValueConst *argv) {
  LEPUSValue val;
  int p;
  double d;

  val = js_thisNumberValue(ctx, this_val);
  if (LEPUS_IsException(val)) return val;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  if (JS_ToFloat64Free(ctx, &d, val)) return LEPUS_EXCEPTION;
  if (LEPUS_IsUndefined(argv[0])) goto to_string;
  if (JS_ToInt32Sat(ctx, &p, argv[0])) return LEPUS_EXCEPTION;
  if (!isfinite(d)) {
  to_string:
    return JS_ToStringFree(ctx, __JS_NewFloat64(ctx, d));
  }
  if (p < 1 || p > 100)
    return LEPUS_ThrowRangeError(ctx, "invalid number of digits");
  return js_dtoa2(ctx, d, 10, p, JS_DTOA_FORMAT_FIXED);
}

static const LEPUSCFunctionListEntry js_number_proto_funcs[] = {
    LEPUS_CFUNC_DEF("toExponential", 1, js_number_toExponential),
    LEPUS_CFUNC_DEF("toFixed", 1, js_number_toFixed),
    LEPUS_CFUNC_DEF("toPrecision", 1, js_number_toPrecision),
    LEPUS_CFUNC_MAGIC_DEF("toString", 1, js_number_toString, 0),
    LEPUS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_number_toString, 1),
    LEPUS_CFUNC_DEF("valueOf", 0, js_number_valueOf),
};

static LEPUSValue js_parseInt(LEPUSContext *ctx, LEPUSValueConst this_val,
                              int argc, LEPUSValueConst *argv) {
  const char *str, *p;
  int radix, flags;
  LEPUSValue ret;

  str = JS_ToCStringLen2_GC(ctx, NULL, argv[0], 0);
  if (!str) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, &str, HANDLE_TYPE_CSTRING);
  if (JS_ToInt32_GC(ctx, &radix, argv[1])) {
    return LEPUS_EXCEPTION;
  }
  if (radix != 0 && (radix < 2 || radix > 36)) {
    ret = LEPUS_NAN;
  } else {
    p = str;
    p += skip_spaces(p);
    flags = ATOD_INT_ONLY | ATOD_ACCEPT_PREFIX_AFTER_SIGN;
    ret = js_atof(ctx, p, nullptr, radix, flags);
  }
  return ret;
}

static LEPUSValue js_parseFloat(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv) {
  const char *str, *p;
  LEPUSValue ret;

  str = JS_ToCStringLen2_GC(ctx, NULL, argv[0], 0);
  if (!str) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, &str, HANDLE_TYPE_CSTRING);
  p = str;
  p += skip_spaces(p);
  ret = js_atof(ctx, p, nullptr, 10, 0);
  return ret;
}

/* Boolean */
static LEPUSValue js_boolean_constructor(LEPUSContext *ctx,
                                         LEPUSValueConst new_target, int argc,
                                         LEPUSValueConst *argv) {
  LEPUSValue val, obj = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  val = LEPUS_NewBool(ctx, JS_ToBool_GC(ctx, argv[0]));
  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  if (!LEPUS_IsUndefined(new_target)) {
    obj = js_create_from_ctor_GC(ctx, new_target, JS_CLASS_BOOLEAN);
    if (!LEPUS_IsException(obj)) JS_SetObjectData(ctx, obj, val);
    return obj;
  } else {
    return val;
  }
}

static LEPUSValue js_thisBooleanValue(LEPUSContext *ctx,
                                      LEPUSValueConst this_val) {
  if (LEPUS_VALUE_IS_BOOL(this_val)) return this_val;

  if (LEPUS_VALUE_IS_OBJECT(this_val)) {
    LEPUSObject *p = LEPUS_VALUE_GET_OBJ(this_val);
    if (p->class_id == JS_CLASS_BOOLEAN) {
      if (LEPUS_VALUE_IS_BOOL(p->u.object_data)) return p->u.object_data;
    }
  }
  return LEPUS_ThrowTypeError(ctx, "not a boolean");
}

static LEPUSValue js_boolean_toString(LEPUSContext *ctx,
                                      LEPUSValueConst this_val, int argc,
                                      LEPUSValueConst *argv) {
  LEPUSValue val = js_thisBooleanValue(ctx, this_val);
  if (LEPUS_IsException(val)) return val;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  return JS_AtomToString_GC(
      ctx, LEPUS_VALUE_GET_BOOL(val) ? JS_ATOM_true : JS_ATOM_false);
}

static LEPUSValue js_boolean_valueOf(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv) {
  return js_thisBooleanValue(ctx, this_val);
}

static const LEPUSCFunctionListEntry js_boolean_proto_funcs[] = {
    LEPUS_CFUNC_DEF("toString", 0, js_boolean_toString),
    LEPUS_CFUNC_DEF("valueOf", 0, js_boolean_valueOf),
};

/* String */

QJS_STATIC int js_string_get_own_property(LEPUSContext *ctx,
                                          LEPUSPropertyDescriptor *desc,
                                          LEPUSValueConst obj, JSAtom prop) {
  LEPUSObject *p;
  JSString *p1;
  uint32_t idx, ch;

  /* This is a class exotic method: obj class_id is JS_CLASS_STRING */
  if (__JS_AtomIsTaggedInt(prop)) {
    p = LEPUS_VALUE_GET_OBJ(obj);
    auto &data = p->u.object_data;

    if (JS_IsSeparableString(data)) {
      data = JS_GetSeparableStringContent_GC(ctx, data);
    }
    if (LEPUS_VALUE_IS_STRING(p->u.object_data)) {
      p1 = LEPUS_VALUE_GET_STRING(p->u.object_data);
      idx = __JS_AtomToUInt32(prop);
      if (idx < p1->len) {
        if (desc) {
          if (p1->is_wide_char)
            ch = p1->u.str16[idx];
          else
            ch = p1->u.str8[idx];
          desc->flags = LEPUS_PROP_ENUMERABLE;
          desc->value = js_new_string_char(ctx, ch);
          desc->getter = LEPUS_UNDEFINED;
          desc->setter = LEPUS_UNDEFINED;
        }
        return TRUE;
      }
    }
  }
  return FALSE;
}

static uint32_t js_string_obj_get_length(LEPUSContext *ctx,
                                         LEPUSValueConst obj) {
  LEPUSObject *p;
  JSString *p1;
  uint32_t len = 0;

  /* This is a class exotic method: obj class_id is JS_CLASS_STRING */
  p = LEPUS_VALUE_GET_OBJ(obj);
  if (LEPUS_VALUE_IS_STRING(p->u.object_data)) {
    p1 = LEPUS_VALUE_GET_STRING(p->u.object_data);
    len = p1->len;
  } else if (JS_IsSeparableString(p->u.object_data)) {
    len = JS_GetSeparableString(p->u.object_data)->len;
  }
  return len;
}

static int js_string_get_own_property_names(LEPUSContext *ctx,
                                            LEPUSPropertyEnum **ptab,
                                            uint32_t *plen,
                                            LEPUSValueConst obj) {
  LEPUSPropertyEnum *tab;
  uint32_t len, i;

  len = js_string_obj_get_length(ctx, obj);
  tab = NULL;
  if (len > 0) {
    /* do not allocate 0 bytes */
    tab = static_cast<LEPUSPropertyEnum *>(lepus_mallocz(
        ctx, sizeof(LEPUSPropertyEnum) * len, ALLOC_TAG_LEPUSPropertyEnum));
    if (!tab) return -1;
    set_heap_obj_len(tab, len);
    for (i = 0; i < len; i++) {
      tab[i].atom = __JS_AtomFromUInt32(i);
    }
  }
  *ptab = tab;
  *plen = len;
  return 0;
}

static int js_string_define_own_property(LEPUSContext *ctx,
                                         LEPUSValueConst this_obj, JSAtom prop,
                                         LEPUSValueConst val,
                                         LEPUSValueConst getter,
                                         LEPUSValueConst setter, int flags) {
  uint32_t idx;
  LEPUSObject *p;
  JSString *p1, *p2;
  HandleScope func_scope(ctx);

  if (__JS_AtomIsTaggedInt(prop)) {
    idx = __JS_AtomToUInt32(prop);
    p = LEPUS_VALUE_GET_OBJ(this_obj);
    auto &data = p->u.object_data;

    if (JS_IsSeparableString(data)) {
      auto content = JS_GetSeparableStringContent_GC(ctx, data);
      data = content;
    }

    if (!LEPUS_VALUE_IS_STRING(p->u.object_data)) goto def;
    p1 = LEPUS_VALUE_GET_STRING(p->u.object_data);
    if (idx >= p1->len) goto def;
    if (!check_define_prop_flags(LEPUS_PROP_ENUMERABLE, flags)) goto fail;
    /* check that the same value is configured */
    if (flags & LEPUS_PROP_HAS_VALUE) {
      if (JS_IsSeparableString(val)) {
        val = JS_GetSeparableStringContentNotDup_GC(ctx, val);
        func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
      }
      if (!LEPUS_VALUE_IS_STRING(val)) goto fail;
      p2 = LEPUS_VALUE_GET_STRING(val);
      if (p2->len != 1) goto fail;
      if (string_get(p1, idx) != string_get(p2, 0)) {
      fail:
        return JS_ThrowTypeErrorOrFalse(ctx, flags,
                                        "property is not configurable");
      }
    }
    return TRUE;
  } else {
  def:
    return JS_DefineProperty_GC(ctx, this_obj, prop, val, getter, setter,
                                flags | LEPUS_PROP_NO_EXOTIC);
  }
}

QJS_STATIC int js_string_delete_property(LEPUSContext *ctx, LEPUSValueConst obj,
                                         JSAtom prop) {
  uint32_t idx;

  if (__JS_AtomIsTaggedInt(prop)) {
    idx = __JS_AtomToUInt32(prop);
    if (idx < js_string_obj_get_length(ctx, obj)) {
      return FALSE;
    }
  }
  return TRUE;
}

static LEPUSValue js_string_constructor(LEPUSContext *ctx,
                                        LEPUSValueConst new_target, int argc,
                                        LEPUSValueConst *argv) {
  LEPUSValue val = LEPUS_UNDEFINED, obj = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  if (argc == 0) {
    val = JS_AtomToString_GC(ctx, JS_ATOM_empty_string);
  } else {
    if (LEPUS_IsUndefined(new_target) && LEPUS_IsSymbol(argv[0])) {
      JSAtomStruct *p =
          static_cast<JSAtomStruct *>(LEPUS_VALUE_GET_PTR(argv[0]));
      val = JS_ConcatString3(
          ctx, "Symbol(",
          JS_AtomToString_GC(ctx, js_get_atom_index(ctx->rt, p)), ")");
    } else {
      val = JS_ToString_GC(ctx, argv[0]);
    }
    if (LEPUS_IsException(val)) return val;
  }
  if (!LEPUS_IsUndefined(new_target)) {
    JSString *p1 = LEPUS_VALUE_GET_STRING(val);

    obj = js_create_from_ctor_GC(ctx, new_target, JS_CLASS_STRING);
    if (!LEPUS_IsException(obj)) {
      JS_SetObjectData(ctx, obj, val);
      JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_length,
                                LEPUS_NewInt32(ctx, p1->len), 0);
    }
    return obj;
  } else {
    return val;
  }
}

static LEPUSValue js_thisStringValue(LEPUSContext *ctx,
                                     LEPUSValueConst this_val) {
  if (LEPUS_IsString(this_val)) return this_val;

  if (LEPUS_VALUE_IS_OBJECT(this_val)) {
    LEPUSObject *p = LEPUS_VALUE_GET_OBJ(this_val);
    if (p->class_id == JS_CLASS_STRING) {
      if (LEPUS_IsString(p->u.object_data)) return p->u.object_data;
    }
  }
  return LEPUS_ThrowTypeError(ctx, "not a string");
}

static LEPUSValue js_string_fromCharCode(LEPUSContext *ctx,
                                         LEPUSValueConst this_val, int argc,
                                         LEPUSValueConst *argv) {
  int i;
  StringBuffer b_s, *b = &b_s;

  string_buffer_init(ctx, b, argc);
  HandleScope func_scope(ctx, &b->str, HANDLE_TYPE_HEAP_OBJ);

  for (i = 0; i < argc; i++) {
    int32_t c;
    if (JS_ToInt32_GC(ctx, &c, argv[i]) ||
        string_buffer_putc16(b, c & 0xffff)) {
      b->str = NULL;
      return LEPUS_EXCEPTION;
    }
  }
  return string_buffer_end(b);
}

static LEPUSValue js_string_fromCodePoint(LEPUSContext *ctx,
                                          LEPUSValueConst this_val, int argc,
                                          LEPUSValueConst *argv) {
  double d;
  int i, c;
  StringBuffer b_s, *b = &b_s;
  HandleScope func_scope(ctx);

  /* XXX: could pre-compute string length if all arguments are LEPUS_TAG_INT
   */

  if (string_buffer_init(ctx, b, argc)) goto fail;
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);
  for (i = 0; i < argc; i++) {
    if (LEPUS_VALUE_IS_INT(argv[i])) {
      c = LEPUS_VALUE_GET_INT(argv[i]);
      if (c < 0 || c > 0x10ffff) goto range_error;
    } else {
      if (JS_ToFloat64_GC(ctx, &d, argv[i])) goto fail;
      if (d < 0 || d > 0x10ffff || (c = static_cast<int>(d)) != d)
        goto range_error;
    }
    if (string_buffer_putc(b, c)) goto fail;
  }
  return string_buffer_end(b);

range_error:
  LEPUS_ThrowRangeError(ctx, "invalid code point");
fail:
  b->str = NULL;
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_string_raw(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv) {
  // raw(temp,...a)
  LEPUSValue cooked, val = LEPUS_UNDEFINED, raw;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  StringBuffer b_s, *b = &b_s;
  int64_t i, n;

  string_buffer_init(ctx, b, 0);
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);
  raw = LEPUS_UNDEFINED;
  cooked = JS_ToObject_GC(ctx, argv[0]);
  if (LEPUS_IsException(cooked)) goto exception;
  func_scope.PushHandle(&cooked, HANDLE_TYPE_LEPUS_VALUE);
  raw = JS_ToObjectFree(
      ctx, JS_GetPropertyInternal_GC(ctx, cooked, JS_ATOM_raw, cooked, 0));
  if (LEPUS_IsException(raw)) goto exception;
  func_scope.PushHandle(&raw, HANDLE_TYPE_LEPUS_VALUE);
  if (js_get_length64(ctx, &n, raw) < 0) goto exception;

  for (i = 0; i < n; i++) {
    val = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, raw, i));
    if (LEPUS_IsException(val)) goto exception;
    string_buffer_concat_value_free(b, val);
    if (i < n - 1 && i + 1 < argc) {
      if (string_buffer_concat_value(b, argv[i + 1])) goto exception;
    }
  }
  return string_buffer_end(b);

exception:
  b->str = NULL;
  return LEPUS_EXCEPTION;
}

#ifdef QJS_UNITTEST
/* only used in test262 */
LEPUSValue js_string_codePointRange_GC(LEPUSContext *ctx,
                                       LEPUSValueConst this_val, int argc,
                                       LEPUSValueConst *argv) {
  uint32_t start, end, i, n;
  StringBuffer b_s, *b = &b_s;

  if (JS_ToInt32_GC(ctx, reinterpret_cast<int32_t *>(&start), argv[0]) ||
      JS_ToInt32_GC(ctx, reinterpret_cast<int32_t *>(&end), argv[1]))
    return LEPUS_EXCEPTION;
  end = min_uint32(end, 0x10ffff + 1);

  if (start > end) {
    start = end;
  }
  n = end - start;
  if (end > 0x10000) {
    n += end - max_uint32(start, 0x10000);
  }
  if (string_buffer_init2(ctx, b, n, end >= 0x100)) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, &b->str, HANDLE_TYPE_HEAP_OBJ);
  for (i = start; i < end; i++) {
    string_buffer_putc(b, i);
  }
  return string_buffer_end(b);
}

#endif
#if 0
static LEPUSValue js_string___isSpace(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
    int c;
    if (JS_ToInt32_GC(ctx, &c, argv[0]))
        return LEPUS_EXCEPTION;
    return LEPUS_NewBool(ctx, lre_is_space(c));
}
#endif

static LEPUSValue js_string_charCodeAt(LEPUSContext *ctx,
                                       LEPUSValueConst this_val, int argc,
                                       LEPUSValueConst *argv) {
  LEPUSValue val, ret = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &ret, HANDLE_TYPE_LEPUS_VALUE);
  JSString *p;
  int idx, c;

  val = JS_ToStringCheckObject(ctx, this_val);
  if (LEPUS_IsException(val)) return val;
  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_STRING(val);
  if (JS_ToInt32Sat(ctx, &idx, argv[0])) {
    return LEPUS_EXCEPTION;
  }
  if (idx < 0 || idx >= p->len) {
    ret = LEPUS_NAN;
  } else {
    if (p->is_wide_char)
      c = p->u.str16[idx];
    else
      c = p->u.str8[idx];
    ret = LEPUS_NewInt32(ctx, c);
  }
  return ret;
}

static LEPUSValue js_string_charAt(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
  LEPUSValue val, ret = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &ret, HANDLE_TYPE_LEPUS_VALUE);
  JSString *p;
  int idx, c;

  val = JS_ToStringCheckObject(ctx, this_val);
  if (LEPUS_IsException(val)) return val;
  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_STRING(val);
  if (JS_ToInt32Sat(ctx, &idx, argv[0])) {
    return LEPUS_EXCEPTION;
  }
  if (idx < 0 || idx >= p->len) {
    ret = js_new_string8_len(ctx, nullptr, 0);
  } else {
    if (p->is_wide_char)
      c = p->u.str16[idx];
    else
      c = p->u.str8[idx];
    ret = js_new_string_char(ctx, c);
  }
  return ret;
}

static LEPUSValue js_string_codePointAt(LEPUSContext *ctx,
                                        LEPUSValueConst this_val, int argc,
                                        LEPUSValueConst *argv) {
  LEPUSValue val, ret;
  JSString *p;
  int idx, c;

  val = JS_ToStringCheckObject(ctx, this_val);
  if (LEPUS_IsException(val)) return val;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_STRING(val);
  if (JS_ToInt32Sat(ctx, &idx, argv[0])) {
    return LEPUS_EXCEPTION;
  }
  if (idx < 0 || idx >= p->len) {
    ret = LEPUS_UNDEFINED;
  } else {
    c = string_getc(p, &idx);
    ret = LEPUS_NewInt32(ctx, c);
  }
  return ret;
}

static LEPUSValue js_string_concat(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
  LEPUSValue r;
  int i;

  /* XXX: Use more efficient method */
  /* XXX: This method is OK if r has a single refcount */
  /* XXX: should use string_buffer? */
  r = JS_ToStringCheckObject(ctx, this_val);
  HandleScope func_scope(ctx, &r, HANDLE_TYPE_LEPUS_VALUE);
  for (i = 0; i < argc; i++) {
    if (LEPUS_IsException(r)) break;
    r = JS_ConcatString_GC(ctx, r, argv[i]);
  }
  return r;
}

static int string_cmp(JSString *p1, JSString *p2, int x1, int x2, int len) {
  int i, c1, c2;
  for (i = 0; i < len; i++) {
    if ((c1 = string_get(p1, x1 + i)) != (c2 = string_get(p2, x2 + i)))
      return c1 - c2;
  }
  return 0;
}

static int string_indexof_char(JSString *p, int c, int from) {
  /* assuming 0 <= from <= p->len */
  int i, len = p->len;
  if (p->is_wide_char) {
    for (i = from; i < len; i++) {
      if (p->u.str16[i] == c) return i;
    }
  } else {
    if ((c & ~0xff) == 0) {
      for (i = from; i < len; i++) {
        if (p->u.str8[i] == (uint8_t)c) return i;
      }
    }
  }
  return -1;
}

static int string_indexof(JSString *p1, JSString *p2, int from) {
  /* assuming 0 <= from <= p1->len */
  int c, i, j, len1 = p1->len, len2 = p2->len;
  if (len2 == 0) return from;
  for (i = from, c = string_get(p2, 0); i + len2 <= len1; i = j + 1) {
    j = string_indexof_char(p1, c, i);
    if (j < 0 || j + len2 > len1) break;
    if (!string_cmp(p1, p2, j + 1, 1, len2 - 1)) return j;
  }
  return -1;
}

static int string_advance_index(JSString *p, int index, BOOL unicode) {
  if (!unicode || (unsigned)index >= p->len || !p->is_wide_char) {
    index++;
  } else {
    string_getc(p, &index);
  }
  return index;
}

static LEPUSValue js_string_indexOf(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv,
                                    int lastIndexOf) {
  LEPUSValue str, v;
  int i, len, v_len, pos, start, stop, ret, inc;
  JSString *p;
  JSString *p1;

  str = JS_ToStringCheckObject(ctx, this_val);
  if (LEPUS_IsException(str)) return str;
  HandleScope func_scope(ctx, &str, HANDLE_TYPE_LEPUS_VALUE);
  v = JS_ToString_GC(ctx, argv[0]);
  if (LEPUS_IsException(v)) goto fail;
  func_scope.PushHandle(&v, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_STRING(str);
  p1 = LEPUS_VALUE_GET_STRING(v);
  len = p->len;
  v_len = p1->len;
  if (lastIndexOf) {
    pos = len - v_len;
    if (argc > 1) {
      double d;
      if (JS_ToFloat64_GC(ctx, &d, argv[1])) goto fail;
      if (!isnan(d)) {
        if (d <= 0)
          pos = 0;
        else if (d < pos)
          pos = d;
      }
    }
    start = pos;
    stop = 0;
    inc = -1;
  } else {
    pos = 0;
    if (argc > 1) {
      if (JS_ToInt32Clamp(ctx, &pos, argv[1], 0, len, 0)) goto fail;
    }
    start = pos;
    stop = len - v_len;
    inc = 1;
  }
  ret = -1;
  if (len >= v_len && inc * (stop - start) >= 0) {
    for (i = start;; i += inc) {
      if (!string_cmp(p, p1, i, 0, v_len)) {
        ret = i;
        break;
      }
      if (i == stop) break;
    }
  }
  return LEPUS_NewInt32(ctx, ret);

fail:
  return LEPUS_EXCEPTION;
}

/* return < 0 if exception or TRUE/FALSE */
QJS_HIDE int js_is_regexp_GC(LEPUSContext *ctx, LEPUSValueConst obj);

static LEPUSValue js_string_includes(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv, int magic) {
  LEPUSValue str, v = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &v, HANDLE_TYPE_LEPUS_VALUE);
  int i, len, v_len, pos, start, stop, ret;
  JSString *p;
  JSString *p1;

  str = JS_ToStringCheckObject(ctx, this_val);
  if (LEPUS_IsException(str)) return str;
  func_scope.PushHandle(&str, HANDLE_TYPE_LEPUS_VALUE);
  ret = js_is_regexp_GC(ctx, argv[0]);
  if (ret) {
    if (ret > 0) LEPUS_ThrowTypeError(ctx, "regex not supported");
    goto fail;
  }
  v = JS_ToString_GC(ctx, argv[0]);
  if (LEPUS_IsException(v)) goto fail;
  p = LEPUS_VALUE_GET_STRING(str);
  p1 = LEPUS_VALUE_GET_STRING(v);
  len = p->len;
  v_len = p1->len;
  pos = (magic & 2) ? len : 0;
  if (argc > 1 && !LEPUS_IsUndefined(argv[1])) {
    if (JS_ToInt32Clamp(ctx, &pos, argv[1], 0, len, 0)) goto fail;
  }
  len -= v_len;
  start = pos;
  stop = len;
  if (magic & 1) {
    stop = pos;
  }
  if (magic & 2) {
    pos -= v_len;
    start = stop = pos;
  }
  ret = 0;
  if (start >= 0 && start <= stop) {
    for (i = start;; i++) {
      if (!string_cmp(p, p1, i, 0, v_len)) {
        ret = 1;
        break;
      }
      if (i == stop) break;
    }
  }
  return LEPUS_NewBool(ctx, ret);

fail:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_string_match(LEPUSContext *ctx, LEPUSValueConst this_val,
                                  int argc, LEPUSValueConst *argv, int atom) {
  // match(rx), search(rx), matchAll(rx)
  // atom is JS_ATOM_Symbol_match, JS_ATOM_Symbol_search, or
  // JS_ATOM_Symbol_matchAll
  LEPUSValueConst O = this_val, regexp = argv[0], args[2];
  HandleScope func_scope(ctx, &O, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&regexp, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushLEPUSValueArrayHandle(args, 2);
  LEPUSValue matcher = LEPUS_UNDEFINED, S, rx = LEPUS_UNDEFINED, result,
             str = LEPUS_UNDEFINED;
  func_scope.PushHandle(&matcher, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&rx, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&str, HANDLE_TYPE_LEPUS_VALUE);
  int args_len;

  if (LEPUS_IsUndefined(O) || LEPUS_IsNull(O))
    return LEPUS_ThrowTypeError(ctx, "cannot convert to object");

  if (!LEPUS_IsUndefined(regexp) && !LEPUS_IsNull(regexp)) {
    matcher = JS_GetPropertyInternal_GC(ctx, regexp, atom, regexp, 0);
    if (LEPUS_IsException(matcher)) return LEPUS_EXCEPTION;
    if (!LEPUS_IsUndefined(matcher) && !LEPUS_IsNull(matcher)) {
      return JS_CallFree_GC(ctx, matcher, regexp, 1, &O);
    }
  }
  S = JS_ToString_GC(ctx, O);
  if (LEPUS_IsException(S)) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&S, HANDLE_TYPE_LEPUS_VALUE);
  args_len = 1;
  args[0] = regexp;
  str = LEPUS_UNDEFINED;
  if (atom == JS_ATOM_Symbol_matchAll) {
    str = JS_NewString_GC(ctx, "g");
    if (LEPUS_IsException(str)) goto fail;
    args[args_len++] = (LEPUSValueConst)str;
  }
  rx = JS_CallConstructor_GC(ctx, ctx->regexp_ctor, args_len, args);
  if (LEPUS_IsException(rx)) {
  fail:
    return LEPUS_EXCEPTION;
  }
  result =
      JS_InvokeFree(ctx, rx, atom, 1, reinterpret_cast<LEPUSValueConst *>(&S));
  return result;
}

static LEPUSValue js_string___GetSubstitution(LEPUSContext *ctx,
                                              LEPUSValueConst this_val,
                                              int argc, LEPUSValueConst *argv) {
  // GetSubstitution(matched, str, position, captures, namedCaptures, rep)
  LEPUSValueConst matched = LEPUS_UNDEFINED, str = LEPUS_UNDEFINED;
  LEPUSValueConst captures = LEPUS_UNDEFINED, namedCaptures = LEPUS_UNDEFINED,
                  rep = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &matched, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&str, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&captures, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&namedCaptures, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&rep, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue capture = LEPUS_UNDEFINED, name = LEPUS_UNDEFINED,
             s = LEPUS_UNDEFINED;
  func_scope.PushHandle(&capture, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&name, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&s, HANDLE_TYPE_LEPUS_VALUE);
  uint32_t position, len, matched_len, captures_len;
  int i, j, j0, k, k1;
  int c, c1;
  StringBuffer b_s, *b = &b_s;
  JSString *sp, *rp;

  matched = argv[0];
  str = argv[1];
  captures = argv[3];
  namedCaptures = argv[4];
  rep = argv[5];

  if (!LEPUS_IsString(rep) || !LEPUS_IsString(str))
    return LEPUS_ThrowTypeError(ctx, "not a string");

  sp = LEPUS_VALUE_GET_STRING(str);
  rp = LEPUS_VALUE_GET_STRING(rep);

  string_buffer_init(ctx, b, 0);
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);

  captures_len = 0;
  if (!LEPUS_IsUndefined(captures)) {
    if (js_get_length32_gc(ctx, &captures_len, captures)) goto exception;
  }
  if (js_get_length32_gc(ctx, &matched_len, matched)) goto exception;
  if (JS_ToInt32_GC(ctx, reinterpret_cast<int32_t *>(&position), argv[2]) < 0)
    goto exception;

  len = rp->len;
  i = 0;
  for (;;) {
    j = string_indexof_char(rp, '$', i);
    if (j < 0 || j + 1 >= len) break;
    string_buffer_concat(b, rp, i, j);
    j0 = j++;
    c = string_get(rp, j++);
    if (c == '$') {
      string_buffer_putc8(b, '$');
    } else if (c == '&') {
      if (string_buffer_concat_value(b, matched)) goto exception;
    } else if (c == '`') {
      string_buffer_concat(b, sp, 0, position);
    } else if (c == '\'') {
      string_buffer_concat(b, sp, position + matched_len, sp->len);
    } else if (c >= '0' && c <= '9') {
      k = c - '0';
      c1 = string_get(rp, j);
      if (c1 >= '0' && c1 <= '9') {
        /* This behavior is specified in ES6 and refined in ECMA 2019 */
        /* ECMA 2019 does not have the extra test, but
           Test262 S15.5.4.11_A3_T1..3 require this behavior */
        k1 = k * 10 + c1 - '0';
        if (k1 >= 1 && k1 < captures_len) {
          k = k1;
          j++;
        }
      }
      if (k >= 1 && k < captures_len) {
        s = JS_GetPropertyInt64(ctx, captures, k);
        if (LEPUS_IsException(s)) goto exception;
        if (!LEPUS_IsUndefined(s)) {
          if (string_buffer_concat_value_free(b, s)) goto exception;
        }
      } else {
        goto norep;
      }
    } else if (c == '<' && !LEPUS_IsUndefined(namedCaptures)) {
      k = string_indexof_char(rp, '>', j);
      if (k < 0) goto norep;
      name = js_sub_string(ctx, rp, j, k);
      if (LEPUS_IsException(name)) goto exception;
      capture = JS_GetPropertyValue_GC(ctx, namedCaptures, name);
      if (LEPUS_IsException(capture)) goto exception;
      if (!LEPUS_IsUndefined(capture)) {
        if (string_buffer_concat_value_free(b, capture)) goto exception;
      }
      j = k + 1;
    } else {
    norep:
      string_buffer_concat(b, rp, j0, j);
    }
    i = j;
  }
  string_buffer_concat(b, rp, i, rp->len);
  return string_buffer_end(b);
exception:
  b->str = NULL;
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_string_replace(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
  // replace(rx, rep)
  LEPUSValueConst O = this_val, searchValue = argv[0], replaceValue = argv[1];
  HandleScope func_scope(ctx, &O, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&searchValue, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&replaceValue, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst args[6];
  func_scope.PushLEPUSValueArrayHandle(args, 6);
  LEPUSValue str, search_str, replaceValue_str, repl_str;
  JSString *sp, *searchp;
  StringBuffer b_s, *b = &b_s;
  int pos, functionalReplace;

  if (LEPUS_IsUndefined(O) || LEPUS_IsNull(O))
    return LEPUS_ThrowTypeError(ctx, "cannot convert to object");

  search_str = LEPUS_UNDEFINED;
  replaceValue_str = LEPUS_UNDEFINED;
  repl_str = LEPUS_UNDEFINED;
  func_scope.PushHandle(&search_str, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&replaceValue_str, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&repl_str, HANDLE_TYPE_LEPUS_VALUE);

  if (!LEPUS_IsUndefined(searchValue) && !LEPUS_IsNull(searchValue)) {
    HandleScope block_scope(ctx->rt);
    LEPUSValue replacer;
    replacer = JS_GetPropertyInternal_GC(
        ctx, searchValue, JS_ATOM_Symbol_replace, searchValue, 0);
    if (LEPUS_IsException(replacer)) return LEPUS_EXCEPTION;
    block_scope.PushHandle(&replacer, HANDLE_TYPE_LEPUS_VALUE);
    if (!LEPUS_IsUndefined(replacer) && !LEPUS_IsNull(replacer)) {
      args[0] = O;
      args[1] = replaceValue;
      return JS_CallFree_GC(ctx, replacer, searchValue, 2, args);
    }
  }
  string_buffer_init(ctx, b, 0);
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);

  str = JS_ToString_GC(ctx, O);
  if (LEPUS_IsException(str)) goto exception;
  func_scope.PushHandle(&str, HANDLE_TYPE_LEPUS_VALUE);
  search_str = JS_ToString_GC(ctx, searchValue);
  if (LEPUS_IsException(search_str)) goto exception;
  functionalReplace = LEPUS_IsFunction(ctx, replaceValue);
  if (!functionalReplace) {
    replaceValue_str = JS_ToString_GC(ctx, replaceValue);
    if (LEPUS_IsException(replaceValue_str)) goto exception;
  }

  sp = LEPUS_VALUE_GET_STRING(str);
  searchp = LEPUS_VALUE_GET_STRING(search_str);

  pos = string_indexof(sp, searchp, 0);
  if (pos < 0) {
    b->str = NULL;
    return str;
  }
  if (functionalReplace) {
    args[0] = search_str;
    args[1] = LEPUS_NewInt32(ctx, pos);
    args[2] = str;
    LEPUSValue ret = JS_Call_GC(ctx, replaceValue, LEPUS_UNDEFINED, 3, args);
    HandleScope block_scope(ctx, &ret, HANDLE_TYPE_LEPUS_VALUE);
    repl_str = JS_ToStringFree(ctx, ret);
  } else {
    args[0] = search_str;
    args[1] = str;
    args[2] = LEPUS_NewInt32(ctx, pos);
    args[3] = LEPUS_UNDEFINED;
    args[4] = LEPUS_UNDEFINED;
    args[5] = replaceValue_str;
    repl_str = js_string___GetSubstitution(ctx, LEPUS_UNDEFINED, 6, args);
  }
  if (LEPUS_IsException(repl_str)) goto exception;

  string_buffer_concat(b, sp, 0, pos);
  string_buffer_concat_value_free(b, repl_str);
  string_buffer_concat(b, sp, pos + searchp->len, sp->len);
  return string_buffer_end(b);

exception:
  b->str = NULL;
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_string_split(LEPUSContext *ctx, LEPUSValueConst this_val,
                                  int argc, LEPUSValueConst *argv) {
  // split(sep, limit)
  LEPUSValueConst O = this_val, separator = argv[0], limit = argv[1];
  LEPUSValueConst args[2];
  LEPUSValue S, A, R, T = LEPUS_UNDEFINED;
  uint32_t lim, lengthA;
  int64_t p, q, s, r, e;
  JSString *sp, *rp;

  if (LEPUS_IsUndefined(O) || LEPUS_IsNull(O))
    return LEPUS_ThrowTypeError(ctx, "cannot convert to object");

  if (!LEPUS_IsUndefined(separator) && !LEPUS_IsNull(separator)) {
    LEPUSValue splitter;
    splitter = JS_GetPropertyInternal_GC(ctx, separator, JS_ATOM_Symbol_split,
                                         separator, 0);
    if (LEPUS_IsException(splitter)) return LEPUS_EXCEPTION;
    if (!LEPUS_IsUndefined(splitter) && !LEPUS_IsNull(splitter)) {
      args[0] = O;
      args[1] = limit;
      return JS_CallFree_GC(ctx, splitter, separator, 2, args);
    }
  }
  S = JS_ToString_GC(ctx, O);
  HandleScope func_scope(ctx, &S, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_IsException(S)) goto exception;
  A = JS_NewArray_GC(ctx);
  func_scope.PushHandle(&A, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_IsException(A)) goto exception;
  lengthA = 0;
  if (LEPUS_IsUndefined(limit)) {
    lim = 0xffffffff;
  } else {
    if (JS_ToInt32_GC(ctx, reinterpret_cast<int32_t *>(&lim), limit) < 0)
      goto exception;
  }
  sp = LEPUS_VALUE_GET_STRING(S);
  s = sp->len;
  R = JS_ToString_GC(ctx, separator);
  if (LEPUS_IsException(R)) goto exception;
  func_scope.PushHandle(&R, HANDLE_TYPE_LEPUS_VALUE);
  rp = LEPUS_VALUE_GET_STRING(R);
  r = rp->len;
  p = 0;
  if (lim == 0) goto done;
  if (LEPUS_IsUndefined(separator)) goto add_tail;
  if (s == 0) {
    if (r != 0) goto add_tail;
    goto done;
  }
  q = p;
  func_scope.PushHandle(&T, HANDLE_TYPE_LEPUS_VALUE);
  for (q = p; (q += !r) <= s - r - !r; q = p = e + r) {
    e = string_indexof(sp, rp, q);
    if (e < 0) break;
    T = js_sub_string(ctx, sp, p, e);
    if (LEPUS_IsException(T)) goto exception;
    if (JS_CreateDataPropertyUint32(ctx, A, lengthA++, T, 0) < 0)
      goto exception;
    if (lengthA == lim) goto done;
  }
add_tail:
  T = js_sub_string(ctx, sp, p, s);
  if (LEPUS_IsException(T)) goto exception;
  if (JS_CreateDataPropertyUint32(ctx, A, lengthA++, T, 0) < 0) goto exception;
done:
  return A;

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_string_substring(LEPUSContext *ctx,
                                      LEPUSValueConst this_val, int argc,
                                      LEPUSValueConst *argv) {
  LEPUSValue str, ret;
  int a, b, start, end;
  JSString *p;

  str = JS_ToStringCheckObject(ctx, this_val);
  if (LEPUS_IsException(str)) return str;
  HandleScope func_scope(ctx, &str, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_STRING(str);
  if (JS_ToInt32Clamp(ctx, &a, argv[0], 0, p->len, 0)) {
    return LEPUS_EXCEPTION;
  }
  b = p->len;
  if (!LEPUS_IsUndefined(argv[1])) {
    if (JS_ToInt32Clamp(ctx, &b, argv[1], 0, p->len, 0)) {
      return LEPUS_EXCEPTION;
    }
  }
  if (a < b) {
    start = a;
    end = b;
  } else {
    start = b;
    end = a;
  }
  ret = js_sub_string(ctx, p, start, end);
  return ret;
}

static LEPUSValue js_string_substr(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
  LEPUSValue str, ret;
  int a, len, n;
  JSString *p;

  str = JS_ToStringCheckObject(ctx, this_val);
  if (LEPUS_IsException(str)) return str;
  HandleScope func_scope(ctx, &str, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_STRING(str);
  len = p->len;
  if (JS_ToInt32Clamp(ctx, &a, argv[0], 0, len, len)) {
    return LEPUS_EXCEPTION;
  }
  n = len - a;
  if (!LEPUS_IsUndefined(argv[1])) {
    if (JS_ToInt32Clamp(ctx, &n, argv[1], 0, len - a, 0)) {
      return LEPUS_EXCEPTION;
    }
  }
  ret = js_sub_string(ctx, p, a, a + n);
  return ret;
}

static LEPUSValue js_string_slice(LEPUSContext *ctx, LEPUSValueConst this_val,
                                  int argc, LEPUSValueConst *argv) {
  LEPUSValue str, ret;
  int len, start, end;
  JSString *p;

  str = JS_ToStringCheckObject(ctx, this_val);
  if (LEPUS_IsException(str)) return str;
  HandleScope func_scope(ctx, &str, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_STRING(str);
  len = p->len;
  if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) {
    return LEPUS_EXCEPTION;
  }
  end = len;
  if (!LEPUS_IsUndefined(argv[1])) {
    if (JS_ToInt32Clamp(ctx, &end, argv[1], 0, len, len)) {
      return LEPUS_EXCEPTION;
    }
  }
  ret = js_sub_string(ctx, p, start, max_int(end, start));
  return ret;
}

static LEPUSValue js_string_pad(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv, int padEnd) {
  LEPUSValue str, v = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &v, HANDLE_TYPE_LEPUS_VALUE);
  StringBuffer b_s, *b = &b_s;
  JSString *p, *p1 = NULL;
  int n, len, c = ' ';

  str = JS_ToStringCheckObject(ctx, this_val);
  if (LEPUS_IsException(str)) goto fail1;
  func_scope.PushHandle(&str, HANDLE_TYPE_LEPUS_VALUE);
  if (JS_ToInt32Sat(ctx, &n, argv[0])) goto fail2;
  p = LEPUS_VALUE_GET_STRING(str);
  len = p->len;
  if (len >= n) return str;
  if (n > JS_STRING_LEN_MAX) {
    LEPUS_ThrowInternalError(ctx, "string too long");
    goto fail2;
  }
  if (argc > 1 && !LEPUS_IsUndefined(argv[1])) {
    v = JS_ToString_GC(ctx, argv[1]);
    if (LEPUS_IsException(v)) goto fail2;
    p1 = LEPUS_VALUE_GET_STRING(v);
    if (p1->len == 0) {
      return str;
    }
    if (p1->len == 1) {
      c = string_get(p1, 0);
      p1 = NULL;
    }
  }
  if (string_buffer_init(ctx, b, n)) goto fail3;
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);
  n -= len;
  if (padEnd) {
    if (string_buffer_concat(b, p, 0, len)) goto fail;
  }
  if (p1) {
    while (n > 0) {
      int chunk = min_int(n, p1->len);
      if (string_buffer_concat(b, p1, 0, chunk)) goto fail;
      n -= chunk;
    }
  } else {
    if (string_buffer_fill(b, c, n)) goto fail;
  }
  if (!padEnd) {
    if (string_buffer_concat(b, p, 0, len)) goto fail;
  }
  return string_buffer_end(b);

fail:
  b->str = NULL;
fail3:
fail2:
fail1:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_string_repeat(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
  HandleScope func_scope(ctx);
  LEPUSValue str;
  StringBuffer b_s, *b = &b_s;
  JSString *p;
  int64_t val;
  int n, len;

  str = JS_ToStringCheckObject(ctx, this_val);
  if (LEPUS_IsException(str)) goto fail;
  func_scope.PushHandle(&str, HANDLE_TYPE_LEPUS_VALUE);
  if (JS_ToInt64Sat(ctx, &val, argv[0])) goto fail;
  if (val < 0 || val > 2147483647) {
    LEPUS_ThrowRangeError(ctx, "invalid repeat count");
    goto fail;
  }
  n = val;
  p = LEPUS_VALUE_GET_STRING(str);
  len = p->len;
  if (len == 0 || n == 1) return str;
  if (val * len > JS_STRING_LEN_MAX) {
    LEPUS_ThrowInternalError(ctx, "string too long");
    goto fail;
  }
  if (string_buffer_init2(ctx, b, n * len, p->is_wide_char)) goto fail;
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);
  if (len == 1) {
    string_buffer_fill(b, string_get(p, 0), n);
  } else {
    while (n-- > 0) {
      string_buffer_concat(b, p, 0, len);
    }
  }
  return string_buffer_end(b);

fail:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_string_trim(LEPUSContext *ctx, LEPUSValueConst this_val,
                                 int argc, LEPUSValueConst *argv, int magic) {
  LEPUSValue str, ret;
  int a, b, len;
  JSString *p;

  str = JS_ToStringCheckObject(ctx, this_val);
  if (LEPUS_IsException(str)) return str;
  HandleScope func_scope(ctx, &str, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_STRING(str);
  a = 0;
  b = len = p->len;
  if (magic & 1) {
    while (a < len && lre_is_space(string_get(p, a))) a++;
  }
  if (magic & 2) {
    while (b > a && lre_is_space(string_get(p, b - 1))) b--;
  }
  ret = js_sub_string(ctx, p, a, b);
  return ret;
}

static LEPUSValue js_string___quote(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
  return JS_ToQuotedString(ctx, this_val);
}

static LEPUSValue js_string_localeCompare(LEPUSContext *ctx,
                                          LEPUSValueConst this_val, int argc,
                                          LEPUSValueConst *argv) {
  LEPUSValue a, b;
  int cmp;

  a = JS_ToStringCheckObject(ctx, this_val);
  if (LEPUS_IsException(a)) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, &a, HANDLE_TYPE_LEPUS_VALUE);
  b = JS_ToString_GC(ctx, argv[0]);
  if (LEPUS_IsException(b)) {
    return LEPUS_EXCEPTION;
  }
  func_scope.PushHandle(&b, HANDLE_TYPE_LEPUS_VALUE);
  cmp = js_string_compare(ctx, LEPUS_VALUE_GET_STRING(a),
                          LEPUS_VALUE_GET_STRING(b));
  return LEPUS_NewInt32(ctx, cmp);
}

static LEPUSValue js_string_toLowerCase(LEPUSContext *ctx,
                                        LEPUSValueConst this_val, int argc,
                                        LEPUSValueConst *argv, int to_lower) {
  LEPUSValue val;
  StringBuffer b_s, *b = &b_s;
  JSString *p;
  int i, c, j, l;
  uint32_t res[LRE_CC_RES_LEN_MAX];

  val = JS_ToStringCheckObject(ctx, this_val);
  if (LEPUS_IsException(val)) return val;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_STRING(val);
  if (p->len == 0) return val;
  if (string_buffer_init(ctx, b, p->len)) goto fail;
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);
  for (i = 0; i < p->len;) {
    c = string_getc(p, &i);
    if (c == 0x3a3 && to_lower && test_final_sigma(p, i - 1)) {
      res[0] = 0x3c2; /* final sigma */
      l = 1;
    } else {
      l = lre_case_conv(res, c, to_lower);
    }
    for (j = 0; j < l; j++) {
      if (string_buffer_putc(b, res[j])) goto fail;
    }
  }
  return string_buffer_end(b);
fail:
  b->str = NULL;
  return LEPUS_EXCEPTION;
}

#ifdef CONFIG_ALL_UNICODE

/* return (-1, NULL) if exception, otherwise (len, buf) */
static int JS_ToUTF32String(LEPUSContext *ctx, uint32_t **pbuf,
                            LEPUSValueConst val1) {
  LEPUSValue val;
  JSString *p;
  uint32_t *buf;
  int i, j, len;

  val = JS_ToString_GC(ctx, val1);
  if (LEPUS_IsException(val)) return -1;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_STRING(val);
  len = p->len;
  /* UTF32 buffer length is len minus the number of correct surrogates pairs
   */
  buf = static_cast<uint32_t *>(lepus_malloc(
      ctx, sizeof(buf[0]) * max_int(len, 1), ALLOC_TAG_WITHOUT_PTR));
  if (!buf) {
    goto fail;
  }
  for (i = j = 0; i < len;) buf[j++] = string_getc(p, &i);
  *pbuf = buf;
  return j;
fail:
  *pbuf = NULL;
  return -1;
}

static LEPUSValue JS_NewUTF32String(LEPUSContext *ctx, const uint32_t *buf,
                                    int len) {
  int i;
  StringBuffer b_s, *b = &b_s;
  if (string_buffer_init(ctx, b, len)) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, &b->str, HANDLE_TYPE_HEAP_OBJ);
  for (i = 0; i < len; i++) {
    if (string_buffer_putc(b, buf[i])) goto fail;
  }
  return string_buffer_end(b);
fail:
  b->str = NULL;
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_string_normalize(LEPUSContext *ctx,
                                      LEPUSValueConst this_val, int argc,
                                      LEPUSValueConst *argv) {
  const char *form, *p;
  size_t form_len;
  int is_compat, buf_len, out_len;
  UnicodeNormalizationEnum n_type;
  LEPUSValue val;
  uint32_t *buf = nullptr, *out_buf = nullptr;
  HandleScope func_scope(ctx, &buf, HANDLE_TYPE_HEAP_OBJ);

  val = JS_ToStringCheckObject(ctx, this_val);
  if (LEPUS_IsException(val)) return val;
  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  buf_len = JS_ToUTF32String(ctx, &buf, val);
  if (buf_len < 0) return LEPUS_EXCEPTION;

  if (argc == 0 || LEPUS_IsUndefined(argv[0])) {
    n_type = UNICODE_NFC;
  } else {
    form = JS_ToCStringLen2_GC(ctx, &form_len, argv[0], 0);
    if (!form) goto fail1;
    func_scope.PushHandle(&form, HANDLE_TYPE_CSTRING);
    p = form;
    if (p[0] != 'N' || p[1] != 'F') goto bad_form;
    p += 2;
    is_compat = FALSE;
    if (*p == 'K') {
      is_compat = TRUE;
      p++;
    }
    if (*p == 'C' || *p == 'D') {
      n_type = static_cast<UnicodeNormalizationEnum>(
          UNICODE_NFC + is_compat * 2 + (*p - 'C'));
      if ((p + 1 - form) != form_len) goto bad_form;
    } else {
    bad_form:
      LEPUS_ThrowRangeError(ctx, "bad normalization form");
    fail1:
      return LEPUS_EXCEPTION;
    }
  }

  out_len =
      unicode_normalize(&out_buf, buf, buf_len, n_type, ctx->rt, ctx, nullptr);
  if (out_len < 0) return LEPUS_EXCEPTION;
  val = JS_NewUTF32String(ctx, out_buf, out_len);
  system_free(out_buf);
  return val;
}
#endif /* CONFIG_ALL_UNICODE */

/* also used for String.prototype.valueOf */
static LEPUSValue js_string_toString(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv) {
  return js_thisStringValue(ctx, this_val);
}

#if 0
static LEPUSValue js_string___toStringCheckObject(LEPUSContext *ctx, LEPUSValueConst this_val,
                                               int argc, LEPUSValueConst *argv) {
    return JS_ToStringCheckObject(ctx, argv[0]);
}

static LEPUSValue js_string___toString(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
    return JS_ToString_GC(ctx, argv[0]);
}

static LEPUSValue js_string___advanceStringIndex(LEPUSContext *ctx, LEPUSValueConst
                                              this_val,
                                              int argc, LEPUSValueConst *argv) {
    LEPUSValue str;
    int idx;
    BOOL is_unicode;
    JSString *p;

    str = JS_ToString_GC(ctx, argv[0]);
    HandleScope func_scope(ctx, &str, HANDLE_TYPE_LEPUS_VALUE);
    if (LEPUS_IsException(str))
        return str;
    if (JS_ToInt32Sat(ctx, &idx, argv[1])) {
        return LEPUS_EXCEPTION;
    }
    is_unicode = JS_ToBool_GC(ctx, argv[2]);
    p = LEPUS_VALUE_GET_STRING(str);
    if (!is_unicode || (unsigned)idx >= p->len || !p->is_wide_char) {
        idx++;
    } else {
        string_getc(p, &idx);
    }
    return LEPUS_NewInt32(ctx, idx);
}
#endif

/* String Iterator */

static LEPUSValue js_string_iterator_next(LEPUSContext *ctx,
                                          LEPUSValueConst this_val, int argc,
                                          LEPUSValueConst *argv, BOOL *pdone,
                                          int magic) {
  JSArrayIteratorData *it;
  uint32_t idx, c, start;
  JSString *p;

  it = static_cast<JSArrayIteratorData *>(
      LEPUS_GetOpaque2(ctx, this_val, JS_CLASS_STRING_ITERATOR));
  if (!it) {
    *pdone = FALSE;
    return LEPUS_EXCEPTION;
  }
  if (LEPUS_IsUndefined(it->obj)) goto done;
  p = LEPUS_VALUE_GET_STRING(it->obj);
  idx = it->idx;
  if (idx >= p->len) {
    it->obj = LEPUS_UNDEFINED;
  done:
    *pdone = TRUE;
    return LEPUS_UNDEFINED;
  }

  start = idx;
  c = string_getc(p, reinterpret_cast<int *>(&idx));
  it->idx = idx;
  *pdone = FALSE;
  if (c <= 0xffff) {
    return js_new_string_char(ctx, c);
  } else {
    return js_new_string16(ctx, p->u.str16 + start, 2);
  }
}

/* ES6 Annex B 2.3.2 etc. */
enum {
  magic_string_anchor,
  magic_string_big,
  magic_string_blink,
  magic_string_bold,
  magic_string_fixed,
  magic_string_fontcolor,
  magic_string_fontsize,
  magic_string_italics,
  magic_string_link,
  magic_string_small,
  magic_string_strike,
  magic_string_sub,
  magic_string_sup,
};

static LEPUSValue js_string_CreateHTML(LEPUSContext *ctx,
                                       LEPUSValueConst this_val, int argc,
                                       LEPUSValueConst *argv, int magic) {
  LEPUSValue str;
  const JSString *p;
  StringBuffer b_s, *b = &b_s;
  static struct {
    const char *tag, *attr;
  } const defs[] = {
      {"a", "name"}, {"big", NULL},     {"blink", NULL},  {"b", NULL},
      {"tt", NULL},  {"font", "color"}, {"font", "size"}, {"i", NULL},
      {"a", "href"}, {"small", NULL},   {"strike", NULL}, {"sub", NULL},
      {"sup", NULL},
  };

  str = JS_ToStringCheckObject(ctx, this_val);
  if (LEPUS_IsException(str)) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, &str, HANDLE_TYPE_LEPUS_VALUE);
  string_buffer_init(ctx, b, 7);
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);
  string_buffer_putc8(b, '<');
  string_buffer_puts8(b, defs[magic].tag);
  if (defs[magic].attr) {
    HandleScope block_scope(ctx->rt);
    // r += " " + attr + "=\"" + value + "\"";
    LEPUSValue value;
    int i;

    string_buffer_putc8(b, ' ');
    string_buffer_puts8(b, defs[magic].attr);
    string_buffer_puts8(b, "=\"");
    value = JS_ToStringCheckObject(ctx, argv[0]);
    if (LEPUS_IsException(value)) {
      b->str = NULL;
      return LEPUS_EXCEPTION;
    }
    block_scope.PushHandle(&value, HANDLE_TYPE_LEPUS_VALUE);
    p = LEPUS_VALUE_GET_STRING(value);
    for (i = 0; i < p->len; i++) {
      int c = string_get(p, i);
      if (c == '"') {
        string_buffer_puts8(b, "&quot;");
      } else {
        string_buffer_putc16(b, c);
      }
    }
    string_buffer_putc8(b, '\"');
  }
  // return r + ">" + str + "</" + tag + ">";
  string_buffer_putc8(b, '>');
  string_buffer_concat_value_free(b, str);
  string_buffer_puts8(b, "</");
  string_buffer_puts8(b, defs[magic].tag);
  string_buffer_putc8(b, '>');
  return string_buffer_end(b);
}

static const LEPUSCFunctionListEntry js_string_funcs[] = {
    LEPUS_CFUNC_DEF("fromCharCode", 1, js_string_fromCharCode),
    LEPUS_CFUNC_DEF("fromCodePoint", 1, js_string_fromCodePoint),
    LEPUS_CFUNC_DEF("raw", 1, js_string_raw),
    // LEPUS_CFUNC_DEF("__toString", 1, js_string___toString ),
    // LEPUS_CFUNC_DEF("__isSpace", 1, js_string___isSpace ),
    // LEPUS_CFUNC_DEF("__toStringCheckObject", 1,
    // js_string___toStringCheckObject
    // ), LEPUS_CFUNC_DEF("__advanceStringIndex", 3,
    // js_string___advanceStringIndex
    // ), LEPUS_CFUNC_DEF("__GetSubstitution", 6, js_string___GetSubstitution
    // ),
};

static const LEPUSCFunctionListEntry js_string_proto_funcs[] = {
    LEPUS_PROP_INT32_DEF("length", 0, LEPUS_PROP_CONFIGURABLE),
    LEPUS_CFUNC_DEF("charCodeAt", 1, js_string_charCodeAt),
    LEPUS_CFUNC_DEF("charAt", 1, js_string_charAt),
    LEPUS_CFUNC_DEF("concat", 1, js_string_concat),
    LEPUS_CFUNC_DEF("codePointAt", 1, js_string_codePointAt),
    LEPUS_CFUNC_MAGIC_DEF("indexOf", 1, js_string_indexOf, 0),
    LEPUS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_string_indexOf, 1),
    LEPUS_CFUNC_MAGIC_DEF("includes", 1, js_string_includes, 0),
    LEPUS_CFUNC_MAGIC_DEF("endsWith", 1, js_string_includes, 2),
    LEPUS_CFUNC_MAGIC_DEF("startsWith", 1, js_string_includes, 1),
    LEPUS_CFUNC_MAGIC_DEF("match", 1, js_string_match, JS_ATOM_Symbol_match),
    LEPUS_CFUNC_MAGIC_DEF("matchAll", 1, js_string_match,
                          JS_ATOM_Symbol_matchAll),
    LEPUS_CFUNC_MAGIC_DEF("search", 1, js_string_match, JS_ATOM_Symbol_search),
    LEPUS_CFUNC_DEF("split", 2, js_string_split),
    LEPUS_CFUNC_DEF("substring", 2, js_string_substring),
    LEPUS_CFUNC_DEF("substr", 2, js_string_substr),
    LEPUS_CFUNC_DEF("slice", 2, js_string_slice),
    LEPUS_CFUNC_DEF("repeat", 1, js_string_repeat),
    LEPUS_CFUNC_DEF("replace", 2, js_string_replace),
    LEPUS_CFUNC_MAGIC_DEF("padEnd", 1, js_string_pad, 1),
    LEPUS_CFUNC_MAGIC_DEF("padStart", 1, js_string_pad, 0),
    LEPUS_CFUNC_MAGIC_DEF("trim", 0, js_string_trim, 3),
    LEPUS_CFUNC_MAGIC_DEF("trimEnd", 0, js_string_trim, 2),
    LEPUS_ALIAS_DEF("trimRight", "trimEnd"),
    LEPUS_CFUNC_MAGIC_DEF("trimStart", 0, js_string_trim, 1),
    LEPUS_ALIAS_DEF("trimLeft", "trimStart"),
    LEPUS_CFUNC_DEF("toString", 0, js_string_toString),
    LEPUS_CFUNC_DEF("valueOf", 0, js_string_toString),
    LEPUS_CFUNC_DEF("__quote", 1, js_string___quote),
    LEPUS_CFUNC_DEF("localeCompare", 1, js_string_localeCompare),
    LEPUS_CFUNC_MAGIC_DEF("toLowerCase", 0, js_string_toLowerCase, 1),
    LEPUS_CFUNC_MAGIC_DEF("toUpperCase", 0, js_string_toLowerCase, 0),
    LEPUS_CFUNC_MAGIC_DEF("toLocaleLowerCase", 0, js_string_toLowerCase, 1),
    LEPUS_CFUNC_MAGIC_DEF("toLocaleUpperCase", 0, js_string_toLowerCase, 0),
    LEPUS_CFUNC_MAGIC_DEF("[Symbol.iterator]", 0, js_create_array_iterator,
                          JS_ITERATOR_KIND_VALUE | 4),
    /* ES6 Annex B 2.3.2 etc. */
    LEPUS_CFUNC_MAGIC_DEF("anchor", 1, js_string_CreateHTML,
                          magic_string_anchor),
    LEPUS_CFUNC_MAGIC_DEF("big", 0, js_string_CreateHTML, magic_string_big),
    LEPUS_CFUNC_MAGIC_DEF("blink", 0, js_string_CreateHTML, magic_string_blink),
    LEPUS_CFUNC_MAGIC_DEF("bold", 0, js_string_CreateHTML, magic_string_bold),
    LEPUS_CFUNC_MAGIC_DEF("fixed", 0, js_string_CreateHTML, magic_string_fixed),
    LEPUS_CFUNC_MAGIC_DEF("fontcolor", 1, js_string_CreateHTML,
                          magic_string_fontcolor),
    LEPUS_CFUNC_MAGIC_DEF("fontsize", 1, js_string_CreateHTML,
                          magic_string_fontsize),
    LEPUS_CFUNC_MAGIC_DEF("italics", 0, js_string_CreateHTML,
                          magic_string_italics),
    LEPUS_CFUNC_MAGIC_DEF("link", 1, js_string_CreateHTML, magic_string_link),
    LEPUS_CFUNC_MAGIC_DEF("small", 0, js_string_CreateHTML, magic_string_small),
    LEPUS_CFUNC_MAGIC_DEF("strike", 0, js_string_CreateHTML,
                          magic_string_strike),
    LEPUS_CFUNC_MAGIC_DEF("sub", 0, js_string_CreateHTML, magic_string_sub),
    LEPUS_CFUNC_MAGIC_DEF("sup", 0, js_string_CreateHTML, magic_string_sup),
};

static const LEPUSCFunctionListEntry js_string_iterator_proto_funcs[] = {
    LEPUS_ITERATOR_NEXT_DEF("next", 0, js_string_iterator_next, 0),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "String Iterator",
                          LEPUS_PROP_CONFIGURABLE),
};

#ifdef CONFIG_ALL_UNICODE
static const LEPUSCFunctionListEntry js_string_proto_normalize[] = {
    LEPUS_CFUNC_DEF("normalize", 0, js_string_normalize),
};
#endif

void JS_AddIntrinsicStringNormalize_GC(LEPUSContext *ctx) {
#ifdef CONFIG_ALL_UNICODE
  JS_SetPropertyFunctionList_GC(ctx, ctx->class_proto[JS_CLASS_STRING],
                                js_string_proto_normalize,
                                countof(js_string_proto_normalize));
#endif
}

/* Math */

/* precondition: a and b are not NaN */
static double js_fmin(double a, double b) {
  if (a == 0 && b == 0) {
    JSFloat64Union a1, b1;
    a1.d = a;
    b1.d = b;
    a1.u64 |= b1.u64;
    return a1.d;
  } else {
    return fmin(a, b);
  }
}

/* precondition: a and b are not NaN */
static double js_fmax(double a, double b) {
  if (a == 0 && b == 0) {
    JSFloat64Union a1, b1;
    a1.d = a;
    b1.d = b;
    a1.u64 &= b1.u64;
    return a1.d;
  } else {
    return fmax(a, b);
  }
}

enum {
  MATH_OP_ABS,
  MATH_OP_FLOOR,
  MATH_OP_CEIL,
  MATH_OP_ROUND,
  MATH_OP_TRUNC,
  MATH_OP_SQRT,
  MATH_OP_FPROUND,
  MATH_OP_ACOS,
  MATH_OP_ASIN,
  MATH_OP_ATAN,
  MATH_OP_ATAN2,
  MATH_OP_COS,
  MATH_OP_EXP,
  MATH_OP_LOG,
  MATH_OP_POW,
  MATH_OP_SIN,
  MATH_OP_TAN,
  MATH_OP_FMOD,
  MATH_OP_REM,
  MATH_OP_SIGN,

  MATH_OP_ADD,
  MATH_OP_SUB,
  MATH_OP_MUL,
  MATH_OP_DIV,
};

static LEPUSValue js_math_min_max(LEPUSContext *ctx, LEPUSValueConst this_val,
                                  int argc, LEPUSValueConst *argv, int magic) {
  BOOL is_max = magic;
  LEPUSValue val = LEPUS_UNDEFINED, ret = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
  double r, a;
  int i;
  uint64_t tag;

  if (unlikely(argc == 0)) {
    return __JS_NewFloat64(ctx, is_max ? -1.0 / 0.0 : 1.0 / 0.0);
  }

  tag = LEPUS_VALUE_GET_TAG(argv[0]);
  if (tag == LEPUS_TAG_INT) {
    int a1, r1 = LEPUS_VALUE_GET_INT(argv[0]);
    for (i = 1; i < argc; i++) {
      tag = LEPUS_VALUE_GET_TAG(argv[i]);
      if (tag != LEPUS_TAG_INT) {
        r = r1;
        goto generic_case;
      }
      a1 = LEPUS_VALUE_GET_INT(argv[i]);
      if (is_max)
        r1 = max_int(r1, a1);
      else
        r1 = min_int(r1, a1);
    }
    return LEPUS_NewInt32(ctx, r1);
  } else {
    if (LEPUS_ToFloat64(ctx, &r, argv[0])) return LEPUS_EXCEPTION;
    i = 1;
  generic_case:
    while (i < argc) {
      if (LEPUS_ToFloat64(ctx, &a, argv[i])) return LEPUS_EXCEPTION;
      if (!isnan(r)) {
        if (isnan(a)) {
          r = a;
        } else {
          if (is_max)
            r = js_fmax(r, a);
          else
            r = js_fmin(r, a);
        }
      }
      i++;
    }
  }
  return LEPUS_NewFloat64(ctx, r);
}

#if 0
/* XXX: should give exact rounding */
/* XXX: correct NaN/Infinity handling */
static LEPUSValue js_math_hypot(LEPUSContext *ctx, LEPUSValueConst this_val,
                             int argc, LEPUSValueConst *argv) {
    bf_t a_s, *a, r_s, *r = &r_s, r2_s, *r2 = &r2_s;
    LEPUSValue val = LEPUS_UNDEFINED;
    HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
    int i;
    BOOL is_float;

    bf_init(ctx->bf_ctx, r);
    bf_set_si(r, 0);
    for (i = 0; i < argc; i++) {
      val = JS_ToNumber(ctx, argv[i]);
      if (LEPUS_IsException(val)) {
          bf_delete(r);
          return val;
      }
      a = JS_ToBigFloat(ctx, &is_float, &a_s, val);
      bf_add(r, r, a, ctx->fp_env.prec, ctx->fp_env.flags);
      if (a == &a_s) {
        bf_delete(a);
      }
    }
    bf_init(ctx->bf_ctx, r2);
    bf_sqrt(r2, r, ctx->fp_env.prec, ctx->fp_env.flags);
    bf_delete(r);
    return JS_NewBigFloat(ctx, r2);
}
#endif

static double js_math_sign(double a) {
  if (isnan(a) || a == 0.0) return a;
  if (a < 0)
    return -1;
  else
    return 1;
}

static double js_math_round(double a) {
  JSFloat64Union u;
  uint64_t frac_mask, one;
  unsigned int e, s;

  u.d = a;
  e = (u.u64 >> 52) & 0x7ff;
  if (e < 1023) {
    /* abs(a) < 1 */
    if (e == (1023 - 1) && u.u64 != 0xbfe0000000000000) {
      /* abs(a) > 0.5 or a = 0.5: return +/-1.0 */
      u.u64 = (u.u64 & ((uint64_t)1 << 63)) | ((uint64_t)1023 << 52);
    } else {
      /* return +/-0.0 */
      u.u64 &= (uint64_t)1 << 63;
    }
  } else if (e < (1023 + 52)) {
    s = u.u64 >> 63;
    one = (uint64_t)1 << (52 - (e - 1023));
    frac_mask = one - 1;
    u.u64 += (one >> 1) - s;
    u.u64 &= ~frac_mask; /* truncate to an integer */
  }
  /* otherwise: abs(a) >= 2^52, or NaN, +/-Infinity: no change */
  return u.d;
}
static LEPUSValue js_math_hypot(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv) {
  double r, a;
  int i;

  r = 0;
  if (argc > 0) {
    if (JS_ToFloat64_GC(ctx, &r, argv[0])) return LEPUS_EXCEPTION;
    if (argc == 1) {
      r = fabs(r);
    } else {
      /* use the built-in function to minimize precision loss */
      for (i = 1; i < argc; i++) {
        if (JS_ToFloat64_GC(ctx, &a, argv[i])) return LEPUS_EXCEPTION;
        r = hypot(r, a);
      }
    }
  }
  return LEPUS_NewFloat64(ctx, r);
}

static double js_math_fround(double a) { return static_cast<float>(a); }

static LEPUSValue js_math_imul(LEPUSContext *ctx, LEPUSValueConst this_val,
                               int argc, LEPUSValueConst *argv) {
  int a, b;

  if (JS_ToInt32_GC(ctx, &a, argv[0])) return LEPUS_EXCEPTION;
  if (JS_ToInt32_GC(ctx, &b, argv[1])) return LEPUS_EXCEPTION;
  /* purposely ignoring overflow */
  return LEPUS_NewInt32(ctx, a * b);
}

static LEPUSValue js_math_clz32(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv) {
  uint32_t a, r;

  if (JS_ToInt32_GC(ctx, reinterpret_cast<int32_t *>(&a), argv[0]))
    return LEPUS_EXCEPTION;
  if (a == 0)
    r = 32;
  else
    r = clz32(a);
  return LEPUS_NewInt32(ctx, r);
}

static const LEPUSCFunctionListEntry js_math_funcs[] = {
    LEPUS_CFUNC_MAGIC_DEF("min", 2, js_math_min_max, 0),
    LEPUS_CFUNC_MAGIC_DEF("max", 2, js_math_min_max, 1),
    LEPUS_CFUNC_SPECIAL_DEF("abs", 1, f_f, fabs),
    LEPUS_CFUNC_SPECIAL_DEF("floor", 1, f_f, floor),
    LEPUS_CFUNC_SPECIAL_DEF("ceil", 1, f_f, ceil),
    LEPUS_CFUNC_SPECIAL_DEF("round", 1, f_f, js_math_round),
    LEPUS_CFUNC_SPECIAL_DEF("sqrt", 1, f_f, sqrt),

    LEPUS_CFUNC_SPECIAL_DEF("acos", 1, f_f, acos),
    LEPUS_CFUNC_SPECIAL_DEF("asin", 1, f_f, asin),
    LEPUS_CFUNC_SPECIAL_DEF("atan", 1, f_f, atan),
    LEPUS_CFUNC_SPECIAL_DEF("atan2", 2, f_f_f, atan2),
    LEPUS_CFUNC_SPECIAL_DEF("cos", 1, f_f, cos),
    LEPUS_CFUNC_SPECIAL_DEF("exp", 1, f_f, exp),
    LEPUS_CFUNC_SPECIAL_DEF("log", 1, f_f, log),
    LEPUS_CFUNC_SPECIAL_DEF("pow", 2, f_f_f, js_pow),
    LEPUS_CFUNC_SPECIAL_DEF("sin", 1, f_f, sin),
    LEPUS_CFUNC_SPECIAL_DEF("tan", 1, f_f, tan),
    /* ES6 */
    LEPUS_CFUNC_SPECIAL_DEF("trunc", 1, f_f, trunc),
    LEPUS_CFUNC_SPECIAL_DEF("sign", 1, f_f, js_math_sign),
    LEPUS_CFUNC_SPECIAL_DEF("cosh", 1, f_f, cosh),
    LEPUS_CFUNC_SPECIAL_DEF("sinh", 1, f_f, sinh),
    LEPUS_CFUNC_SPECIAL_DEF("tanh", 1, f_f, tanh),
    LEPUS_CFUNC_SPECIAL_DEF("acosh", 1, f_f, acosh),
    LEPUS_CFUNC_SPECIAL_DEF("asinh", 1, f_f, asinh),
    LEPUS_CFUNC_SPECIAL_DEF("atanh", 1, f_f, atanh),
    LEPUS_CFUNC_SPECIAL_DEF("expm1", 1, f_f, expm1),
    LEPUS_CFUNC_SPECIAL_DEF("log1p", 1, f_f, log1p),
    LEPUS_CFUNC_SPECIAL_DEF("log2", 1, f_f, log2),
    LEPUS_CFUNC_SPECIAL_DEF("log10", 1, f_f, log10),
    LEPUS_CFUNC_SPECIAL_DEF("cbrt", 1, f_f, cbrt),
    LEPUS_CFUNC_DEF("hypot", 2, js_math_hypot),
    LEPUS_CFUNC_DEF("random", 0, js_math_random),
    LEPUS_CFUNC_SPECIAL_DEF("fround", 1, f_f, js_math_fround),
    LEPUS_CFUNC_DEF("imul", 2, js_math_imul),
    LEPUS_CFUNC_DEF("clz32", 1, js_math_clz32),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "Math",
                          LEPUS_PROP_CONFIGURABLE),
    LEPUS_PROP_DOUBLE_DEF("E", 2.718281828459045, 0),
    LEPUS_PROP_DOUBLE_DEF("LN10", 2.302585092994046, 0),
    LEPUS_PROP_DOUBLE_DEF("LN2", 0.6931471805599453, 0),
    LEPUS_PROP_DOUBLE_DEF("LOG2E", 1.4426950408889634, 0),
    LEPUS_PROP_DOUBLE_DEF("LOG10E", 0.4342944819032518, 0),
    LEPUS_PROP_DOUBLE_DEF("PI", 3.141592653589793, 0),
    LEPUS_PROP_DOUBLE_DEF("SQRT1_2", 0.7071067811865476, 0),
    LEPUS_PROP_DOUBLE_DEF("SQRT2", 1.4142135623730951, 0),
};

static const LEPUSCFunctionListEntry js_math_obj[] = {
    LEPUS_OBJECT_DEF("Math", js_math_funcs, countof(js_math_funcs),
                     LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE),
};

/* Date */

#if 0
/* OS dependent: return the UTC time in ms since 1970. */
static LEPUSValue js___date_now(LEPUSContext *ctx, LEPUSValueConst this_val,
                             int argc, LEPUSValueConst *argv) {
    int64_t d;
    struct timeval tv;
    gettimeofday(&tv, NULL);
    d = (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000);
    return JS_NewInt64_GC(ctx, d);
}
#endif

/* OS dependent: return the UTC time in microseconds since 1970. */
static LEPUSValue js___date_clock(LEPUSContext *ctx, LEPUSValueConst this_val,
                                  int argc, LEPUSValueConst *argv) {
  int64_t d;
  struct timeval tv;
  gettimeofday(&tv, NULL);
  d = (int64_t)tv.tv_sec * 1000000 + tv.tv_usec;
  return JS_NewInt64_GC(ctx, d);
}

#if 0
static LEPUSValue js___date_getTimezoneOffset(LEPUSContext *ctx, LEPUSValueConst this_val,
                                           int argc, LEPUSValueConst *argv) {
    double dd;

    if (JS_ToFloat64_GC(ctx, &dd, argv[0]))
        return LEPUS_EXCEPTION;
    if (isnan(dd))
        return __JS_NewFloat64(ctx, dd);
    else
        return LEPUS_NewInt32(ctx, getTimezoneOffset((int64_t)dd));
}

static LEPUSValue js_get_prototype_from_ctor(LEPUSContext *ctx, LEPUSValueConst ctor,
                                          LEPUSValueConst def_proto) {
    LEPUSValue proto;
    proto = JS_GetPropertyInternal_GC(ctx, ctor, JS_ATOM_prototype, ctor, 0);
    if (LEPUS_IsException(proto))
        return proto;
    if (!LEPUS_IsObject(proto)) {
        proto = def_proto;
    }
    return proto;
}

/* create a new date object */
static LEPUSValue js___date_create(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv) {
    LEPUSValue obj = LEPUS_UNDEFINED, proto;
    HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
    proto = js_get_prototype_from_ctor(ctx, argv[0], argv[1]);
    if (LEPUS_IsException(proto))
        return proto;
    func_scope.PushHandle(&proto, HANDLE_TYPE_LEPUS_VALUE);
    obj = JS_NewObjectProtoClass_GC(ctx, proto, JS_CLASS_DATE);
    if (!LEPUS_IsException(obj))
        JS_SetObjectData(ctx, obj, argv[2]);
    return obj;
}
#endif

/* RegExp */

/* create a string containing the RegExp bytecode */
static LEPUSValue js_compile_regexp(LEPUSContext *ctx, LEPUSValueConst pattern,
                                    LEPUSValueConst flags) {
  const char *str = NULL;
  HandleScope func_scope(ctx, &str, HANDLE_TYPE_CSTRING);
  int re_flags, mask;
  uint8_t *re_bytecode_buf;
  size_t i, len;
  int re_bytecode_len;
  LEPUSValue ret;
  char error_msg[64];

  re_flags = 0;
  if (!LEPUS_IsUndefined(flags)) {
    str = JS_ToCStringLen2_GC(ctx, &len, flags, 0);
    if (!str) return LEPUS_EXCEPTION;
    /* XXX: re_flags = LRE_FLAG_OCTAL unless strict mode? */
    for (i = 0; i < len; i++) {
      switch (str[i]) {
        case 'g':
          mask = LRE_FLAG_GLOBAL;
          break;
        case 'i':
          mask = LRE_FLAG_IGNORECASE;
          break;
        case 'm':
          mask = LRE_FLAG_MULTILINE;
          break;
        case 's':
          mask = LRE_FLAG_DOTALL;
          break;
        case 'u':
          mask = LRE_FLAG_UTF16;
          break;
        case 'y':
          mask = LRE_FLAG_STICKY;
          break;
        default:
          goto bad_flags;
      }
      if ((re_flags & mask) != 0) {
      bad_flags:
        return LEPUS_ThrowSyntaxError(ctx, "invalid regular expression flags");
      }
      re_flags |= mask;
    }
  }

  str = JS_ToCStringLen2_GC(ctx, &len, pattern, !(re_flags & LRE_FLAG_UTF16));
  if (!str) return LEPUS_EXCEPTION;
  re_bytecode_buf = lre_compile(&re_bytecode_len, error_msg, sizeof(error_msg),
                                str, len, re_flags, ctx);
  if (!re_bytecode_buf) {
    LEPUS_ThrowSyntaxError(ctx, "%s", error_msg);
    return LEPUS_EXCEPTION;
  }

  ret = js_new_string8(ctx, re_bytecode_buf, re_bytecode_len);
  lre_free(re_bytecode_buf);
  return ret;
}

/* create a RegExp object from a string containing the RegExp bytecode
   and the source pattern */
LEPUSValue js_regexp_constructor_internal_gc(LEPUSContext *ctx,
                                             LEPUSValueConst ctor,
                                             LEPUSValue pattern,
                                             LEPUSValue bc) {
  LEPUSValue obj;
  LEPUSObject *p;
  JSRegExp *re;
  HandleScope func_scope(ctx);

  if (JS_IsSeparableString(pattern)) {
    auto content = JS_GetSeparableStringContent_GC(ctx, pattern);
    pattern = content;
    func_scope.PushHandle(&pattern, HANDLE_TYPE_LEPUS_VALUE);
  }

  if (JS_IsSeparableString(bc)) {
    auto content = JS_GetSeparableStringContent_GC(ctx, bc);
    bc = content;
    func_scope.PushHandle(&bc, HANDLE_TYPE_LEPUS_VALUE);
  }

  /* sanity check */
  if (!LEPUS_VALUE_IS_STRING(bc) || !LEPUS_VALUE_IS_STRING(pattern)) {
    LEPUS_ThrowTypeError(ctx, "string expected");
  fail:
    return LEPUS_EXCEPTION;
  }

  obj = js_create_from_ctor_GC(ctx, ctor, JS_CLASS_REGEXP);
  if (LEPUS_IsException(obj)) goto fail;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_OBJ(obj);
  re = &p->u.regexp;
  re->pattern = LEPUS_VALUE_GET_STRING(pattern);
  re->bytecode = LEPUS_VALUE_GET_STRING(bc);
  JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_lastIndex, LEPUS_NewInt32(ctx, 0),
                            LEPUS_PROP_WRITABLE);
  return obj;
}

/* return < 0 if exception or TRUE/FALSE */
QJS_HIDE int js_is_regexp_GC(LEPUSContext *ctx, LEPUSValueConst obj) {
  LEPUSValue m;

  if (!LEPUS_IsObject(obj)) return FALSE;
  m = JS_GetPropertyInternal_GC(ctx, obj, JS_ATOM_Symbol_match, obj, 0);
  if (LEPUS_IsException(m)) return -1;
  if (!LEPUS_IsUndefined(m)) return JS_ToBoolFree_GC(ctx, m);
  return js_get_regexp(ctx, obj, FALSE) != NULL;
}

static LEPUSValue js_regexp_constructor(LEPUSContext *ctx,
                                        LEPUSValueConst new_target, int argc,
                                        LEPUSValueConst *argv) {
  LEPUSValue pattern = LEPUS_UNDEFINED, flags = LEPUS_UNDEFINED,
             bc = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &pattern, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&flags, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&bc, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst pat, flags1;
  JSRegExp *re;
  int pat_is_regexp;

  pat = argv[0];
  flags1 = argv[1];
  pat_is_regexp = js_is_regexp_GC(ctx, pat);
  if (pat_is_regexp < 0) return LEPUS_EXCEPTION;
  if (LEPUS_IsUndefined(new_target)) {
    /* called as a function */
    new_target = JS_GetActiveFunction(ctx);
    if (pat_is_regexp && LEPUS_IsUndefined(flags1)) {
      LEPUSValue ctor;
      BOOL res;
      ctor = JS_GetPropertyInternal_GC(ctx, pat, JS_ATOM_constructor, pat, 0);
      if (LEPUS_IsException(ctor)) return ctor;
      res = js_same_value(ctx, ctor, new_target);
      if (res) return pat;
    }
  }
  re = js_get_regexp(ctx, pat, FALSE);
  if (re) {
    pattern = LEPUS_MKPTR(LEPUS_TAG_STRING, re->pattern);
    if (LEPUS_IsUndefined(flags1)) {
      bc = LEPUS_MKPTR(LEPUS_TAG_STRING, re->bytecode);
      goto no_compilation;
    } else {
      flags = JS_ToString_GC(ctx, flags1);
      if (LEPUS_IsException(flags)) goto fail;
    }
  } else {
    flags = LEPUS_UNDEFINED;
    if (pat_is_regexp) {
      pattern = JS_GetPropertyInternal_GC(ctx, pat, JS_ATOM_source, pat, 0);
      if (LEPUS_IsException(pattern)) goto fail;
      if (LEPUS_IsUndefined(flags1)) {
        flags = JS_GetPropertyInternal_GC(ctx, pat, JS_ATOM_flags, pat, 0);
        if (LEPUS_IsException(flags)) goto fail;
      } else {
        flags = flags1;
      }
    } else {
      pattern = pat;
      flags = flags1;
    }
    if (LEPUS_IsUndefined(pattern)) {
      pattern = JS_AtomToString_GC(ctx, JS_ATOM_empty_string);
    } else {
      pattern = JS_ToString_GC(ctx, pattern);
      if (LEPUS_IsException(pattern)) goto fail;
    }
  }
  bc = js_compile_regexp(ctx, pattern, flags);
  if (LEPUS_IsException(bc)) goto fail;
no_compilation:
  return js_regexp_constructor_internal_gc(ctx, new_target, pattern, bc);
fail:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_regexp_compile(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
  JSRegExp *re1, *re;
  LEPUSValueConst pattern1, flags1;
  LEPUSValue bc = LEPUS_UNDEFINED, pattern = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &bc, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&pattern, HANDLE_TYPE_LEPUS_VALUE);

  re = js_get_regexp(ctx, this_val, TRUE);
  if (!re) return LEPUS_EXCEPTION;
  pattern1 = argv[0];
  flags1 = argv[1];
  re1 = js_get_regexp(ctx, pattern1, FALSE);
  if (re1) {
    if (!LEPUS_IsUndefined(flags1))
      return LEPUS_ThrowTypeError(ctx, "flags must be undefined");
    pattern = LEPUS_MKPTR(LEPUS_TAG_STRING, re1->pattern);
    bc = LEPUS_MKPTR(LEPUS_TAG_STRING, re1->bytecode);
  } else {
    bc = LEPUS_UNDEFINED;
    if (LEPUS_IsUndefined(pattern1))
      pattern = JS_AtomToString_GC(ctx, JS_ATOM_empty_string);
    else
      pattern = JS_ToString_GC(ctx, pattern1);
    if (LEPUS_IsException(pattern)) goto fail;
    bc = js_compile_regexp(ctx, pattern, flags1);
    if (LEPUS_IsException(bc)) goto fail;
  }
  re->pattern = LEPUS_VALUE_GET_STRING(pattern);
  re->bytecode = LEPUS_VALUE_GET_STRING(bc);
  if (JS_SetPropertyInternal_GC(ctx, this_val, JS_ATOM_lastIndex,
                                LEPUS_NewInt32(ctx, 0), LEPUS_PROP_THROW) < 0)
    return LEPUS_EXCEPTION;
  return this_val;
fail:
  return LEPUS_EXCEPTION;
}

#if 0
static LEPUSValue js_regexp_get___source(LEPUSContext *ctx, LEPUSValueConst this_val) {
    JSRegExp *re = js_get_regexp(ctx, this_val, TRUE);
    if (!re)
        return LEPUS_EXCEPTION;
    return LEPUS_MKPTR(LEPUS_TAG_STRING, re->pattern);
}

static LEPUSValue js_regexp_get___flags(LEPUSContext *ctx, LEPUSValueConst this_val) {
    JSRegExp *re = js_get_regexp(ctx, this_val, TRUE);
    int flags;

    if (!re)
      return LEPUS_EXCEPTION;
    flags = lre_get_flags(re->bytecode->u.str8);
    return LEPUS_NewInt32(ctx, flags);
}
#endif

static LEPUSValue js_regexp_get_source(LEPUSContext *ctx,
                                       LEPUSValueConst this_val) {
  JSRegExp *re;
  JSString *p;
  StringBuffer b_s, *b = &b_s;
  int i, n, c, c2, bra;

  if (LEPUS_VALUE_IS_NOT_OBJECT(this_val))
    return JS_ThrowTypeErrorNotAnObject(ctx);

  if (js_same_value(ctx, this_val, ctx->class_proto[JS_CLASS_REGEXP]))
    goto empty_regex;

  re = js_get_regexp(ctx, this_val, TRUE);
  if (!re) return LEPUS_EXCEPTION;

  p = re->pattern;

  if (p->len == 0) {
  empty_regex:
    return JS_NewString_GC(ctx, "(?:)");
  }
  string_buffer_init2(ctx, b, p->len, p->is_wide_char);
  HandleScope func_scope(ctx, &b->str, HANDLE_TYPE_HEAP_OBJ);

  /* Escape '/' and newline sequences as needed */
  bra = 0;
  for (i = 0, n = p->len; i < n;) {
    c2 = -1;
    switch (c = string_get(p, i++)) {
      case '\\':
        if (i < n) c2 = string_get(p, i++);
        break;
      case ']':
        bra = 0;
        break;
      case '[':
        if (!bra) {
          if (i < n && string_get(p, i) == ']') c2 = string_get(p, i++);
          bra = 1;
        }
        break;
      case '\n':
        c = '\\';
        c2 = 'n';
        break;
      case '\r':
        c = '\\';
        c2 = 'r';
        break;
      case '/':
        if (!bra) {
          c = '\\';
          c2 = '/';
        }
        break;
    }
    string_buffer_putc16(b, c);
    if (c2 >= 0) string_buffer_putc16(b, c2);
  }
  return string_buffer_end(b);
}

static LEPUSValue js_regexp_get_flag(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int mask) {
  JSRegExp *re;
  int flags;

  if (LEPUS_VALUE_IS_NOT_OBJECT(this_val))
    return JS_ThrowTypeErrorNotAnObject(ctx);

  if (js_same_value(ctx, this_val, ctx->class_proto[JS_CLASS_REGEXP]))
    return LEPUS_UNDEFINED;

  re = js_get_regexp(ctx, this_val, TRUE);
  if (!re) return LEPUS_EXCEPTION;

  flags = lre_get_flags(re->bytecode->u.str8);
  return LEPUS_NewBool(ctx, (flags & mask) != 0);
}

static LEPUSValue js_regexp_get_flags(LEPUSContext *ctx,
                                      LEPUSValueConst this_val) {
  char str[8], *p = str;
  int res;

  if (LEPUS_VALUE_IS_NOT_OBJECT(this_val))
    return JS_ThrowTypeErrorNotAnObject(ctx);

  res = JS_ToBoolFree_GC(ctx, JS_GetPropertyInternal_GC(
                                  ctx, this_val, JS_ATOM_global, this_val, 0));
  if (res < 0) goto exception;
  if (res) *p++ = 'g';
  res =
      JS_ToBoolFree_GC(ctx, JS_GetPropertyStr_GC(ctx, this_val, "ignoreCase"));
  if (res < 0) goto exception;
  if (res) *p++ = 'i';
  res = JS_ToBoolFree_GC(ctx, JS_GetPropertyStr_GC(ctx, this_val, "multiline"));
  if (res < 0) goto exception;
  if (res) *p++ = 'm';
  res = JS_ToBoolFree_GC(ctx, JS_GetPropertyStr_GC(ctx, this_val, "dotAll"));
  if (res < 0) goto exception;
  if (res) *p++ = 's';
  res = JS_ToBoolFree_GC(ctx, JS_GetPropertyInternal_GC(
                                  ctx, this_val, JS_ATOM_unicode, this_val, 0));
  if (res < 0) goto exception;
  if (res) *p++ = 'u';
  res = JS_ToBoolFree_GC(ctx, JS_GetPropertyStr_GC(ctx, this_val, "sticky"));
  if (res < 0) goto exception;
  if (res) *p++ = 'y';
  return JS_NewStringLen_GC(ctx, str, p - str);

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_regexp_toString(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv) {
  LEPUSValue pattern, flags;
  StringBuffer b_s, *b = &b_s;

  if (!LEPUS_IsObject(this_val)) return JS_ThrowTypeErrorNotAnObject(ctx);

  string_buffer_init(ctx, b, 0);
  HandleScope func_scope(ctx, &b->str, HANDLE_TYPE_HEAP_OBJ);
  string_buffer_putc8(b, '/');
  pattern =
      JS_GetPropertyInternal_GC(ctx, this_val, JS_ATOM_source, this_val, 0);
  func_scope.PushHandle(&pattern, HANDLE_TYPE_LEPUS_VALUE);
  if (string_buffer_concat_value_free(b, pattern)) goto fail;
  string_buffer_putc8(b, '/');
  flags = JS_GetPropertyInternal_GC(ctx, this_val, JS_ATOM_flags, this_val, 0);
  func_scope.PushHandle(&flags, HANDLE_TYPE_LEPUS_VALUE);
  if (string_buffer_concat_value_free(b, flags)) goto fail;
  return string_buffer_end(b);

fail:
  b->str = NULL;
  return LEPUS_EXCEPTION;
}

void *lre_realloc(void *opaque, void *ptr, size_t size, int alloc_tag) {
  LEPUSContext *ctx = static_cast<LEPUSContext *>(opaque);
  /* No LEPUS exception is raised here */
  return lepus_realloc_rt(ctx->rt, ptr, size, alloc_tag);
}

#if defined(__WASI_SDK__) || defined(QJS_UNITTEST)
static void js_clear_regexp_caputre_property(LEPUSContext *ctx,
                                             LEPUSValue constructor,
                                             uint32_t caputre_count) {
  if (LEPUS_IsUndefined(constructor) || LEPUS_IsException(constructor)) return;
  char regexp_capture_name[] = {'$', '0', 0};
  LEPUSValue null_string = JS_NewString_GC(ctx, "");
  HandleScope func_scope(ctx, &null_string, HANDLE_TYPE_LEPUS_VALUE);
  uint32_t i;

  int32_t prop_flags = LEPUS_PROP_CONFIGURABLE | LEPUS_PROP_WRITABLE;
  if (caputre_count == 0) {
    JS_DefinePropertyValueStr_GC(ctx, constructor, "$&", null_string,
                                 prop_flags);
    caputre_count = 1;
  }

  for (i = caputre_count, regexp_capture_name[1] += i; i < 10;
       ++i, ++regexp_capture_name[1]) {
    JS_DefinePropertyValueStr_GC(ctx, constructor, regexp_capture_name,
                                 null_string, prop_flags);
  }
}
#endif

static LEPUSValue js_regexp_exec(LEPUSContext *ctx, LEPUSValueConst this_val,
                                 int argc, LEPUSValueConst *argv) {
  JSRegExp *re = js_get_regexp(ctx, this_val, TRUE);
  JSString *str;
  LEPUSValue str_val, obj = LEPUS_UNDEFINED, val, groups = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&groups, HANDLE_TYPE_LEPUS_VALUE);
  uint8_t *re_bytecode;
  int ret;
  uint8_t **capture, *str_buf;
  int capture_count, shift, i, re_flags;
  int64_t last_index;
  const char *group_name_ptr;
  LEPUSValue regexp_obj = LEPUS_UNDEFINED;

  if (!re) return LEPUS_EXCEPTION;
  str_val = JS_ToString_GC(ctx, argv[0]);
  if (LEPUS_IsException(str_val)) return str_val;
  func_scope.PushHandle(&str_val, HANDLE_TYPE_LEPUS_VALUE);
  val =
      JS_GetPropertyInternal_GC(ctx, this_val, JS_ATOM_lastIndex, this_val, 0);
  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_IsException(val) || JS_ToLengthFree(ctx, &last_index, val)) {
    return LEPUS_EXCEPTION;
  }
  re_bytecode = re->bytecode->u.str8;
  re_flags = lre_get_flags(re_bytecode);
  if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) {
    last_index = 0;
  }
  str = LEPUS_VALUE_GET_STRING(str_val);
  capture_count = lre_get_capture_count(re_bytecode);
  capture = NULL;
  if (capture_count > 0) {
    capture = static_cast<uint8_t **>(lepus_malloc(
        ctx, sizeof(capture[0]) * capture_count * 2, ALLOC_TAG_WITHOUT_PTR));
    if (!capture) {
      return LEPUS_EXCEPTION;
    }
  }
  func_scope.PushHandle(capture, HANDLE_TYPE_DIR_HEAP_OBJ);
  shift = str->is_wide_char;
  str_buf = str->u.str8;
  if (last_index > str->len) {
    ret = 2;
  } else {
    ret = lre_exec(capture, re_bytecode, str_buf, last_index, str->len, shift,
                   ctx);
  }
  obj = LEPUS_NULL;
  if (ret != 1) {
    if (ret >= 0) {
      if (ret == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) {
        if (JS_SetPropertyInternal_GC(ctx, this_val, JS_ATOM_lastIndex,
                                      LEPUS_NewInt32(ctx, 0),
                                      LEPUS_PROP_THROW) < 0)
          goto fail;
      }
    } else {
      LEPUS_ThrowInternalError(ctx, "out of memory in regexp execution");
      goto fail;
    }
  } else {
    int prop_flags;
    if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) {
      if (JS_SetPropertyInternal_GC(
              ctx, this_val, JS_ATOM_lastIndex,
              LEPUS_NewInt32(ctx, (capture[1] - str_buf) >> shift),
              LEPUS_PROP_THROW) < 0)
        goto fail;
    }
    obj = JS_NewArray_GC(ctx);
    if (LEPUS_IsException(obj)) goto fail;
    prop_flags = LEPUS_PROP_C_W_E | LEPUS_PROP_THROW;
    group_name_ptr = NULL;
    if (re_flags & LRE_FLAG_NAMED_GROUPS) {
      uint32_t re_bytecode_len;
      groups = JS_NewObjectProto_GC(ctx, LEPUS_NULL);
      if (LEPUS_IsException(groups)) goto fail;
      re_bytecode_len = get_u32(re_bytecode + 3);
      group_name_ptr =
          reinterpret_cast<char *>(re_bytecode + 7 + re_bytecode_len);
    }

#if defined(__WASI_SDK__) || defined(QJS_UNITTEST)
    static constexpr const char *regexp_capture_name[] = {
        "$&", "$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9"};
    regexp_obj = JS_GetPropertyInternal_GC(ctx, this_val, JS_ATOM_constructor,
                                           this_val, 0);
    bool regexp_invalid =
        LEPUS_IsUndefined(regexp_obj) || LEPUS_IsException(regexp_obj);
#endif

    LEPUSValue val = LEPUS_UNDEFINED;
    func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
    for (i = 0; i < capture_count; i++) {
      int start, end;
      if (capture[2 * i] == NULL || capture[2 * i + 1] == NULL) {
        val = LEPUS_UNDEFINED;
      } else {
        start = (capture[2 * i] - str_buf) >> shift;
        end = (capture[2 * i + 1] - str_buf) >> shift;
        val = js_sub_string(ctx, str, start, end);
        if (LEPUS_IsException(val)) goto fail;
      }

#if defined(__WASI_SDK__) || defined(QJS_UNITTEST)
      if (!regexp_invalid &&
          i < (sizeof(regexp_capture_name) / sizeof(regexp_capture_name[0]))) {
        JS_DefinePropertyValueStr_GC(ctx, regexp_obj, regexp_capture_name[i],
                                     val, prop_flags);
      }
#endif

      if (group_name_ptr && i > 0) {
        if (*group_name_ptr) {
          if (JS_DefinePropertyValueStr_GC(ctx, groups, group_name_ptr, val,
                                           prop_flags) < 0) {
            goto fail;
          }
        }
        group_name_ptr += strlen(group_name_ptr) + 1;
      }
      if (JS_DefinePropertyValueUint32_GC(ctx, obj, i, val, prop_flags) < 0)
        goto fail;
    }

#if defined(__WASI_SDK__) || defined(QJS_UNITTEST)
    js_clear_regexp_caputre_property(ctx, regexp_obj, capture_count);
#endif

    if (JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_groups, groups,
                                  prop_flags) < 0)
      goto fail;
    if (JS_DefinePropertyValue_GC(
            ctx, obj, JS_ATOM_index,
            LEPUS_NewInt32(ctx, (capture[0] - str_buf) >> shift),
            prop_flags) < 0)
      goto fail;
    if (JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_input, str_val,
                                  prop_flags) < 0)
      goto fail1;
  }
  return obj;
fail:
fail1:
  return LEPUS_EXCEPTION;
}

/* delete partions of a string that match a given regex */
static LEPUSValue JS_RegExpDelete(LEPUSContext *ctx, LEPUSValueConst this_val,
                                  LEPUSValueConst arg) {
  JSRegExp *re = js_get_regexp(ctx, this_val, TRUE);
  JSString *str;
  LEPUSValue str_val, val = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  uint8_t *re_bytecode;
  int ret;
  uint8_t **capture, *str_buf;
  int capture_count, shift, re_flags;
  int next_src_pos, start, end;
  int64_t last_index;
  StringBuffer b_s, *b = &b_s;

  if (!re) return LEPUS_EXCEPTION;

  string_buffer_init(ctx, b, 0);
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);

  capture = NULL;
  str_val = JS_ToString_GC(ctx, arg);
  if (LEPUS_IsException(str_val)) goto fail;
  func_scope.PushHandle(&str_val, HANDLE_TYPE_LEPUS_VALUE);
  str = LEPUS_VALUE_GET_STRING(str_val);
  re_bytecode = re->bytecode->u.str8;
  re_flags = lre_get_flags(re_bytecode);
  if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) {
    last_index = 0;
  } else {
    val = JS_GetPropertyInternal_GC(ctx, this_val, JS_ATOM_lastIndex, this_val,
                                    0);
    if (LEPUS_IsException(val) || JS_ToLengthFree(ctx, &last_index, val))
      goto fail;
  }
  capture_count = lre_get_capture_count(re_bytecode);
  if (capture_count > 0) {
    capture = static_cast<uint8_t **>(lepus_malloc(
        ctx, sizeof(capture[0]) * capture_count * 2, ALLOC_TAG_WITHOUT_PTR));
    if (!capture) goto fail;
    func_scope.PushHandle(capture, HANDLE_TYPE_DIR_HEAP_OBJ);
  }
  shift = str->is_wide_char;
  str_buf = str->u.str8;
  next_src_pos = 0;
  for (;;) {
    if (last_index > str->len) break;

    ret = lre_exec(capture, re_bytecode, str_buf, last_index, str->len, shift,
                   ctx);
    if (ret != 1) {
      if (ret >= 0) {
        if (ret == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) {
          if (JS_SetPropertyInternal_GC(ctx, this_val, JS_ATOM_lastIndex,
                                        LEPUS_NewInt32(ctx, 0),
                                        LEPUS_PROP_THROW) < 0)
            goto fail;
        }
      } else {
        LEPUS_ThrowInternalError(ctx, "out of memory in regexp execution");
        goto fail;
      }
      break;
    }
    start = (capture[0] - str_buf) >> shift;
    end = (capture[1] - str_buf) >> shift;
    last_index = end;
    if (next_src_pos < start) {
      if (string_buffer_concat(b, str, next_src_pos, start)) goto fail;
    }
    next_src_pos = end;
    if (!(re_flags & LRE_FLAG_GLOBAL)) {
      if (JS_SetPropertyInternal_GC(ctx, this_val, JS_ATOM_lastIndex,
                                    LEPUS_NewInt32(ctx, end),
                                    LEPUS_PROP_THROW) < 0)
        goto fail;
      break;
    }
    if (end == start) {
      if (!(re_flags & LRE_FLAG_UTF16) || (unsigned)end >= str->len ||
          !str->is_wide_char) {
        end++;
      } else {
        string_getc(str, &end);
      }
    }
    last_index = end;
  }
  if (string_buffer_concat(b, str, next_src_pos, str->len)) goto fail;
  return string_buffer_end(b);
fail:
  b->str = NULL;
  return LEPUS_EXCEPTION;
}

static LEPUSValue JS_RegExpExec(LEPUSContext *ctx, LEPUSValueConst r,
                                LEPUSValueConst s) {
  LEPUSValue method, ret;

  method = JS_GetPropertyInternal_GC(ctx, r, JS_ATOM_exec, r, 0);
  if (LEPUS_IsException(method)) return method;
  if (LEPUS_IsFunction(ctx, method)) {
    ret = JS_CallFree_GC(ctx, method, r, 1, &s);
    if (LEPUS_IsException(ret)) return ret;
    if (!LEPUS_IsObject(ret) && !LEPUS_IsNull(ret)) {
      return LEPUS_ThrowTypeError(
          ctx, "RegExp exec method must return an object or null");
    }
    return ret;
  }
  return js_regexp_exec(ctx, r, 1, &s);
}

#if 0
static LEPUSValue js_regexp___RegExpExec(LEPUSContext *ctx, LEPUSValueConst this_val,
                                      int argc, LEPUSValueConst *argv) {
    return JS_RegExpExec(ctx, argv[0], argv[1]);
}
static LEPUSValue js_regexp___RegExpDelete(LEPUSContext *ctx, LEPUSValueConst this_val,
                                        int argc, LEPUSValueConst *argv) {
    return JS_RegExpDelete(ctx, argv[0], argv[1]);
}
#endif

static LEPUSValue js_regexp_test(LEPUSContext *ctx, LEPUSValueConst this_val,
                                 int argc, LEPUSValueConst *argv) {
  LEPUSValue val;
  BOOL ret;

  val = JS_RegExpExec(ctx, this_val, argv[0]);
  if (LEPUS_IsException(val)) return LEPUS_EXCEPTION;
  ret = !LEPUS_IsNull(val);
  return LEPUS_NewBool(ctx, ret);
}

static LEPUSValue js_regexp_Symbol_match(LEPUSContext *ctx,
                                         LEPUSValueConst this_val, int argc,
                                         LEPUSValueConst *argv) {
  // [Symbol.match](str)
  LEPUSValueConst rx = this_val;
  HandleScope func_scope(ctx, &rx, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue A, S, result, matchStr;
  int global, n, fullUnicode, isEmpty;
  JSString *p;

  if (!LEPUS_IsObject(rx)) return JS_ThrowTypeErrorNotAnObject(ctx);

  A = LEPUS_UNDEFINED;
  result = LEPUS_UNDEFINED;
  matchStr = LEPUS_UNDEFINED;
  S = JS_ToString_GC(ctx, argv[0]);
  if (LEPUS_IsException(S)) goto exception;
  func_scope.PushHandle(&A, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&result, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&matchStr, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&S, HANDLE_TYPE_LEPUS_VALUE);

  global = JS_ToBoolFree_GC(
      ctx, JS_GetPropertyInternal_GC(ctx, rx, JS_ATOM_global, rx, 0));
  if (global < 0) goto exception;

  if (!global) {
    A = JS_RegExpExec(ctx, rx, S);
  } else {
    fullUnicode = JS_ToBoolFree_GC(
        ctx, JS_GetPropertyInternal_GC(ctx, rx, JS_ATOM_unicode, rx, 0));
    if (fullUnicode < 0) goto exception;

    if (JS_SetPropertyInternal_GC(ctx, rx, JS_ATOM_lastIndex,
                                  LEPUS_NewInt32(ctx, 0), LEPUS_PROP_THROW) < 0)
      goto exception;
    A = JS_NewArray_GC(ctx);
    if (LEPUS_IsException(A)) goto exception;
    n = 0;
    for (;;) {
      result = JS_RegExpExec(ctx, rx, S);
      if (LEPUS_IsException(result)) goto exception;
      if (LEPUS_IsNull(result)) break;
      matchStr = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, result, 0));
      if (LEPUS_IsException(matchStr)) goto exception;
      isEmpty = JS_IsEmptyString(matchStr);
      if (JS_SetPropertyInt64_GC(ctx, A, n++, matchStr) < 0) goto exception;
      if (isEmpty) {
        int64_t thisIndex, nextIndex;
        if (JS_ToLengthFree(ctx, &thisIndex,
                            JS_GetPropertyInternal_GC(
                                ctx, rx, JS_ATOM_lastIndex, rx, 0)) < 0)
          goto exception;
        p = LEPUS_VALUE_GET_STRING(S);
        nextIndex = thisIndex + 1;
        if (thisIndex < p->len)
          nextIndex = string_advance_index(p, thisIndex, fullUnicode);
        if (JS_SetPropertyInternal_GC(ctx, rx, JS_ATOM_lastIndex,
                                      JS_NewInt64_GC(ctx, nextIndex),
                                      LEPUS_PROP_THROW) < 0)
          goto exception;
      }
    }
    if (n == 0) {
      A = LEPUS_NULL;
    }
  }
  return A;

exception:
  return LEPUS_EXCEPTION;
}

typedef struct JSRegExpStringIteratorData {
  LEPUSValue iterating_regexp;
  LEPUSValue iterated_string;
  BOOL global;
  BOOL unicode;
  BOOL done;
} JSRegExpStringIteratorData;

static LEPUSValue js_regexp_string_iterator_next(LEPUSContext *ctx,
                                                 LEPUSValueConst this_val,
                                                 int argc,
                                                 LEPUSValueConst *argv,
                                                 BOOL *pdone, int magic) {
  JSRegExpStringIteratorData *it;
  LEPUSValueConst R = LEPUS_UNDEFINED, S = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &R, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&S, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue matchStr = LEPUS_UNDEFINED, match = LEPUS_UNDEFINED;
  func_scope.PushHandle(&matchStr, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&match, HANDLE_TYPE_LEPUS_VALUE);
  JSString *sp;

  it = static_cast<JSRegExpStringIteratorData *>(
      LEPUS_GetOpaque2(ctx, this_val, JS_CLASS_REGEXP_STRING_ITERATOR));
  if (!it) goto exception;
  if (it->done) {
    *pdone = TRUE;
    return LEPUS_UNDEFINED;
  }
  R = it->iterating_regexp;
  S = it->iterated_string;
  match = JS_RegExpExec(ctx, R, S);
  if (LEPUS_IsException(match)) goto exception;
  if (LEPUS_IsNull(match)) {
    it->done = TRUE;
    *pdone = TRUE;
    return LEPUS_UNDEFINED;
  } else if (it->global) {
    matchStr = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, match, 0));
    if (LEPUS_IsException(matchStr)) goto exception;
    if (JS_IsEmptyString(matchStr)) {
      int64_t thisIndex, nextIndex;
      if (JS_ToLengthFree(
              ctx, &thisIndex,
              JS_GetPropertyInternal_GC(ctx, R, JS_ATOM_lastIndex, R, 0)) < 0)
        goto exception;
      sp = LEPUS_VALUE_GET_STRING(S);
      nextIndex = string_advance_index(sp, thisIndex, it->unicode);
      if (JS_SetPropertyInternal_GC(ctx, R, JS_ATOM_lastIndex,
                                    LEPUS_NewInt32(ctx, nextIndex),
                                    LEPUS_PROP_THROW) < 0)
        goto exception;
    }
  } else {
    it->done = TRUE;
  }
  *pdone = FALSE;
  return match;
exception:
  *pdone = FALSE;
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_regexp_Symbol_matchAll(LEPUSContext *ctx,
                                            LEPUSValueConst this_val, int argc,
                                            LEPUSValueConst *argv) {
  // [Symbol.matchAll](str)
  LEPUSValueConst R = this_val;
  HandleScope func_scope(ctx, &R, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue S, C, flags, matcher, iter;
  LEPUSValueConst args[2];
  func_scope.PushLEPUSValueArrayHandle(args, 2);
  JSString *strp;
  int64_t lastIndex;
  JSRegExpStringIteratorData *it;

  if (!LEPUS_IsObject(R)) return JS_ThrowTypeErrorNotAnObject(ctx);

  C = LEPUS_UNDEFINED;
  flags = LEPUS_UNDEFINED;
  matcher = LEPUS_UNDEFINED;
  iter = LEPUS_UNDEFINED;
  func_scope.PushHandle(&flags, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&matcher, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&iter, HANDLE_TYPE_LEPUS_VALUE);

  S = JS_ToString_GC(ctx, argv[0]);
  if (LEPUS_IsException(S)) goto exception;
  func_scope.PushHandle(&S, HANDLE_TYPE_LEPUS_VALUE);
  C = JS_SpeciesConstructor(ctx, R, ctx->regexp_ctor);
  if (LEPUS_IsException(C)) goto exception;
  func_scope.PushHandle(&C, HANDLE_TYPE_LEPUS_VALUE);
  flags = JS_ToStringFree(
      ctx, JS_GetPropertyInternal_GC(ctx, R, JS_ATOM_flags, R, 0));
  if (LEPUS_IsException(flags)) goto exception;
  args[0] = R;
  args[1] = flags;
  matcher = JS_CallConstructor_GC(ctx, C, 2, args);
  if (LEPUS_IsException(matcher)) goto exception;
  if (JS_ToLengthFree(
          ctx, &lastIndex,
          JS_GetPropertyInternal_GC(ctx, R, JS_ATOM_lastIndex, R, 0)))
    goto exception;
  if (JS_SetPropertyInternal_GC(ctx, matcher, JS_ATOM_lastIndex,
                                LEPUS_NewInt32(ctx, lastIndex),
                                LEPUS_PROP_THROW) < 0)
    goto exception;

  iter = JS_NewObjectClass_GC(ctx, JS_CLASS_REGEXP_STRING_ITERATOR);
  if (LEPUS_IsException(iter)) goto exception;
  func_scope.PushHandle(&iter, HANDLE_TYPE_LEPUS_VALUE);
  it = static_cast<JSRegExpStringIteratorData *>(
      lepus_malloc(ctx, sizeof(*it), ALLOC_TAG_JSRegExpStringIteratorData));
  if (!it) goto exception;
  it->iterating_regexp = matcher;
  it->iterated_string = S;
  strp = LEPUS_VALUE_GET_STRING(flags);
  it->global = string_indexof_char(strp, 'g', 0) >= 0;
  it->unicode = string_indexof_char(strp, 'u', 0) >= 0;
  it->done = FALSE;
  LEPUS_SetOpaque(iter, it);

  return iter;
exception:
  return LEPUS_EXCEPTION;
}

static int value_buffer_init(LEPUSContext *ctx, ValueBuffer *b) {
  b->ctx = ctx;
  b->len = 0;
  b->size = 4;
  b->error_status = 0;
  b->arr = b->def;
  return 0;
}

static void value_buffer_free(ValueBuffer *b) {
  b->arr = b->def;
  b->size = 4;
  b->len = 0;
}

static int value_buffer_append(ValueBuffer *b, LEPUSValue val) {
  if (b->error_status) return -1;

  if (b->len >= b->size) {
    int new_size = (b->len + (b->len >> 1) + 31) & ~16;
    size_t slack;
    LEPUSValue *new_arr;

    if (b->arr == b->def) {
      new_arr = static_cast<LEPUSValue *>(
          lepus_realloc2(b->ctx, NULL, sizeof(*b->arr) * new_size, &slack,
                         ALLOC_TAG_WITHOUT_PTR));
      if (new_arr) memcpy(new_arr, b->def, sizeof b->def);
    } else {
      new_arr = static_cast<LEPUSValue *>(
          lepus_realloc2(b->ctx, b->arr, sizeof(*b->arr) * new_size, &slack,
                         ALLOC_TAG_WITHOUT_PTR));
    }
    if (!new_arr) {
      value_buffer_free(b);
      b->error_status = -1;
      return -1;
    }
    new_size += slack / sizeof(*new_arr);
    b->arr = new_arr;
    b->size = new_size;
  }
  b->arr[b->len++] = val;
  return 0;
}

static int js_is_standard_regexp(LEPUSContext *ctx, LEPUSValueConst rx) {
  LEPUSValue val;
  int res;

  val = JS_GetPropertyInternal_GC(ctx, rx, JS_ATOM_constructor, rx, 0);
  if (LEPUS_IsException(val)) return -1;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  // rx.constructor === RegExp
  res = js_same_value(ctx, val, ctx->regexp_ctor);
  if (res) {
    val = JS_GetPropertyInternal_GC(ctx, rx, JS_ATOM_exec, rx, 0);
    if (LEPUS_IsException(val)) return -1;
    // rx.exec === RE_exec
    res = JS_IsCFunction(ctx, val, js_regexp_exec, 0);
  }
  return res;
}

static LEPUSValue js_regexp_Symbol_replace(LEPUSContext *ctx,
                                           LEPUSValueConst this_val, int argc,
                                           LEPUSValueConst *argv) {
  // [Symbol.replace](str, rep)
  LEPUSValueConst rx = this_val, rep = argv[1];
  HandleScope func_scope(ctx, &rx, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&rep, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst args[6];
  func_scope.PushLEPUSValueArrayHandle(args, 6);
  LEPUSValue str, rep_val, matched, tab, rep_str, namedCaptures, res;
  JSString *sp, *rp;
  StringBuffer b_s, *b = &b_s;
  ValueBuffer v_b, *results = &v_b;
  int nextSourcePosition, n, j, functionalReplace, is_global, fullUnicode;
  uint32_t nCaptures;
  int64_t position;

  if (!LEPUS_IsObject(rx)) return JS_ThrowTypeErrorNotAnObject(ctx);

  string_buffer_init(ctx, b, 0);
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);
  value_buffer_init(ctx, results);
  func_scope.PushHandle(results, HANDLE_TYPE_VALUE_BUFFER);

  rep_val = LEPUS_UNDEFINED;
  matched = LEPUS_UNDEFINED;
  tab = LEPUS_UNDEFINED;
  rep_str = LEPUS_UNDEFINED;
  namedCaptures = LEPUS_UNDEFINED;
  func_scope.PushHandle(&rep_val, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&matched, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&tab, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&rep_str, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&namedCaptures, HANDLE_TYPE_LEPUS_VALUE);

  str = JS_ToString_GC(ctx, argv[0]);
  if (LEPUS_IsException(str)) goto exception;
  func_scope.PushHandle(&str, HANDLE_TYPE_LEPUS_VALUE);

  sp = LEPUS_VALUE_GET_STRING(str);
  rp = NULL;
  functionalReplace = LEPUS_IsFunction(ctx, rep);
  if (!functionalReplace) {
    rep_val = JS_ToString_GC(ctx, rep);
    if (LEPUS_IsException(rep_val)) goto exception;
    rp = LEPUS_VALUE_GET_STRING(rep_val);
  }
  fullUnicode = 0;
  is_global = JS_ToBoolFree_GC(
      ctx, JS_GetPropertyInternal_GC(ctx, rx, JS_ATOM_global, rx, 0));
  if (is_global < 0) goto exception;
  if (is_global) {
    fullUnicode = JS_ToBoolFree_GC(
        ctx, JS_GetPropertyInternal_GC(ctx, rx, JS_ATOM_unicode, rx, 0));
    if (fullUnicode < 0) goto exception;
    if (JS_SetPropertyInternal_GC(ctx, rx, JS_ATOM_lastIndex,
                                  LEPUS_NewInt32(ctx, 0), LEPUS_PROP_THROW) < 0)
      goto exception;
  }

  if (rp && rp->len == 0 && is_global && js_is_standard_regexp(ctx, rx)) {
    /* use faster version for simple cases */
    res = JS_RegExpDelete(ctx, rx, str);
    goto done;
  }
  for (;;) {
    HandleScope block_scope(ctx->rt);
    LEPUSValue result;
    result = JS_RegExpExec(ctx, rx, str);
    if (LEPUS_IsException(result)) goto exception;
    if (LEPUS_IsNull(result)) break;
    block_scope.PushHandle(&result, HANDLE_TYPE_LEPUS_VALUE);
    if (value_buffer_append(results, result) < 0) goto exception;
    if (!is_global) break;
    matched = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, result, 0));
    if (LEPUS_IsException(matched)) goto exception;
    if (JS_IsEmptyString(matched)) {
      /* always advance of at least one char */
      int64_t thisIndex, nextIndex;
      if (JS_ToLengthFree(
              ctx, &thisIndex,
              JS_GetPropertyInternal_GC(ctx, rx, JS_ATOM_lastIndex, rx, 0)) < 0)
        goto exception;
      nextIndex = string_advance_index(sp, thisIndex, fullUnicode);
      if (JS_SetPropertyInternal_GC(ctx, rx, JS_ATOM_lastIndex,
                                    LEPUS_NewInt32(ctx, nextIndex),
                                    LEPUS_PROP_THROW) < 0)
        goto exception;
    }
  }
  nextSourcePosition = 0;
  for (j = 0; j < results->len; j++) {
    HandleScope block_scope(ctx->rt);
    LEPUSValueConst result;
    result = results->arr[j];
    block_scope.PushHandle(&result, HANDLE_TYPE_LEPUS_VALUE);
    if (js_get_length32_gc(ctx, &nCaptures, result) < 0) goto exception;
    matched = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, result, 0));
    if (LEPUS_IsException(matched)) goto exception;
    if (JS_ToLengthFree(
            ctx, &position,
            JS_GetPropertyInternal_GC(ctx, result, JS_ATOM_index, result, 0)))
      goto exception;
    if (position > sp->len)
      position = sp->len;
    else if (position < 0)
      position = 0;
    /* ignore substition if going backward (can happen
       with custom regexp object) */
    tab = JS_NewArray_GC(ctx);
    if (LEPUS_IsException(tab)) goto exception;
    if (JS_SetPropertyInt64_GC(ctx, tab, 0, matched) < 0) goto exception;
    for (n = 1; n < nCaptures; n++) {
      HandleScope block_scope(ctx->rt);
      LEPUSValue capN;
      capN = JS_GetPropertyInt64(ctx, result, n);
      block_scope.PushHandle(&capN, HANDLE_TYPE_LEPUS_VALUE);
      if (LEPUS_IsException(capN)) goto exception;
      if (!LEPUS_IsUndefined(capN)) {
        capN = JS_ToStringFree(ctx, capN);
        if (LEPUS_IsException(capN)) goto exception;
      }
      if (JS_SetPropertyInt64_GC(ctx, tab, n, capN) < 0) goto exception;
    }
    namedCaptures =
        JS_GetPropertyInternal_GC(ctx, result, JS_ATOM_groups, result, 0);
    if (LEPUS_IsException(namedCaptures)) goto exception;
    if (functionalReplace) {
      if (JS_SetPropertyInt64_GC(ctx, tab, n++, LEPUS_NewInt32(ctx, position)) <
          0)
        goto exception;
      if (JS_SetPropertyInt64_GC(ctx, tab, n++, str) < 0) goto exception;
      if (!LEPUS_IsUndefined(namedCaptures)) {
        if (JS_SetPropertyInt64_GC(ctx, tab, n++, namedCaptures) < 0)
          goto exception;
      }
      args[0] = LEPUS_UNDEFINED;
      args[1] = tab;
      LEPUSValue res = js_function_apply_gc(ctx, rep, 2, args, 0);
      HandleScope block_scope(ctx, &res, HANDLE_TYPE_LEPUS_VALUE);
      rep_str = JS_ToStringFree(ctx, res);
    } else {
      args[0] = matched;
      args[1] = str;
      args[2] = LEPUS_NewInt32(ctx, position);
      args[3] = tab;
      args[4] = namedCaptures;
      args[5] = rep_val;
      rep_str = js_string___GetSubstitution(ctx, LEPUS_UNDEFINED, 6, args);
    }
    if (LEPUS_IsException(rep_str)) goto exception;
    if (position >= nextSourcePosition) {
      string_buffer_concat(b, sp, nextSourcePosition, position);
      string_buffer_concat_value(b, rep_str);
      nextSourcePosition = position + LEPUS_VALUE_GET_STRING(matched)->len;
    }
  }
  string_buffer_concat(b, sp, nextSourcePosition, sp->len);
  res = string_buffer_end(b);
  goto done1;

exception:
  res = LEPUS_EXCEPTION;
done:
  b->str = NULL;
done1:
  value_buffer_free(results);
  return res;
}

static LEPUSValue js_regexp_Symbol_search(LEPUSContext *ctx,
                                          LEPUSValueConst this_val, int argc,
                                          LEPUSValueConst *argv) {
  LEPUSValueConst rx = this_val;
  HandleScope func_scope(ctx, &rx, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue str, previousLastIndex, currentLastIndex, result, index;

  if (!LEPUS_IsObject(rx)) return JS_ThrowTypeErrorNotAnObject(ctx);

  result = LEPUS_UNDEFINED;
  currentLastIndex = LEPUS_UNDEFINED;
  previousLastIndex = LEPUS_UNDEFINED;
  str = JS_ToString_GC(ctx, argv[0]);
  if (LEPUS_IsException(str)) goto exception;
  func_scope.PushHandle(&str, HANDLE_TYPE_LEPUS_VALUE);

  previousLastIndex =
      JS_GetPropertyInternal_GC(ctx, rx, JS_ATOM_lastIndex, rx, 0);
  if (LEPUS_IsException(previousLastIndex)) goto exception;
  func_scope.PushHandle(&previousLastIndex, HANDLE_TYPE_LEPUS_VALUE);

  if (!js_same_value(ctx, previousLastIndex, LEPUS_NewInt32(ctx, 0))) {
    if (JS_SetPropertyInternal_GC(ctx, rx, JS_ATOM_lastIndex,
                                  LEPUS_NewInt32(ctx, 0),
                                  LEPUS_PROP_THROW) < 0) {
      goto exception;
    }
  }
  result = JS_RegExpExec(ctx, rx, str);
  if (LEPUS_IsException(result)) goto exception;
  func_scope.PushHandle(&result, HANDLE_TYPE_LEPUS_VALUE);
  currentLastIndex =
      JS_GetPropertyInternal_GC(ctx, rx, JS_ATOM_lastIndex, rx, 0);
  if (LEPUS_IsException(currentLastIndex)) goto exception;
  func_scope.PushHandle(&currentLastIndex, HANDLE_TYPE_LEPUS_VALUE);
  if (js_same_value(ctx, currentLastIndex, previousLastIndex)) {
  } else {
    if (JS_SetPropertyInternal_GC(ctx, rx, JS_ATOM_lastIndex, previousLastIndex,
                                  LEPUS_PROP_THROW) < 0) {
      previousLastIndex = LEPUS_UNDEFINED;
      goto exception;
    }
  }

  if (LEPUS_IsNull(result)) {
    return LEPUS_NewInt32(ctx, -1);
  } else {
    index = JS_GetPropertyInternal_GC(ctx, result, JS_ATOM_index, result, 0);
    return index;
  }

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_regexp_Symbol_split(LEPUSContext *ctx,
                                         LEPUSValueConst this_val, int argc,
                                         LEPUSValueConst *argv) {
  // [Symbol.split](str, limit)
  LEPUSValueConst rx = this_val;
  HandleScope func_scope(ctx, &rx, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst args[2];
  func_scope.PushLEPUSValueArrayHandle(args, 2);
  LEPUSValue str, ctor, splitter, A, flags, z, sub = LEPUS_UNDEFINED;
  func_scope.PushHandle(&sub, HANDLE_TYPE_LEPUS_VALUE);
  JSString *strp;
  uint32_t lim, size, p, q;
  int unicodeMatching;
  int64_t lengthA, e, numberOfCaptures, i;

  if (!LEPUS_IsObject(rx)) return JS_ThrowTypeErrorNotAnObject(ctx);

  ctor = LEPUS_UNDEFINED;
  splitter = LEPUS_UNDEFINED;
  A = LEPUS_UNDEFINED;
  flags = LEPUS_UNDEFINED;
  z = LEPUS_UNDEFINED;
  func_scope.PushHandle(&ctor, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&splitter, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&A, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&flags, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&z, HANDLE_TYPE_LEPUS_VALUE);
  str = JS_ToString_GC(ctx, argv[0]);
  if (LEPUS_IsException(str)) goto exception;
  func_scope.PushHandle(&str, HANDLE_TYPE_LEPUS_VALUE);
  ctor = JS_SpeciesConstructor(ctx, rx, ctx->regexp_ctor);
  if (LEPUS_IsException(ctor)) goto exception;
  flags = JS_ToStringFree(
      ctx, JS_GetPropertyInternal_GC(ctx, rx, JS_ATOM_flags, rx, 0));
  if (LEPUS_IsException(flags)) goto exception;
  strp = LEPUS_VALUE_GET_STRING(flags);
  unicodeMatching = string_indexof_char(strp, 'u', 0) >= 0;
  if (string_indexof_char(strp, 'y', 0) < 0) {
    flags = JS_ConcatString3(ctx, "", flags, "y");
    if (LEPUS_IsException(flags)) goto exception;
  }
  args[0] = rx;
  args[1] = flags;
  splitter = JS_CallConstructor_GC(ctx, ctor, 2, args);
  if (LEPUS_IsException(splitter)) goto exception;
  A = JS_NewArray_GC(ctx);
  if (LEPUS_IsException(A)) goto exception;
  lengthA = 0;
  if (LEPUS_IsUndefined(argv[1])) {
    lim = 0xffffffff;
  } else {
    if (JS_ToInt32_GC(ctx, reinterpret_cast<int32_t *>(&lim), argv[1]) < 0)
      goto exception;
    if (lim == 0) goto done;
  }
  strp = LEPUS_VALUE_GET_STRING(str);
  p = q = 0;
  size = strp->len;
  if (size == 0) {
    z = JS_RegExpExec(ctx, splitter, str);
    if (LEPUS_IsException(z)) goto exception;
    if (LEPUS_IsNull(z)) goto add_tail;
    goto done;
  }
  while (q < size) {
    if (JS_SetPropertyInternal_GC(ctx, splitter, JS_ATOM_lastIndex,
                                  LEPUS_NewInt32(ctx, q), LEPUS_PROP_THROW) < 0)
      goto exception;
    z = JS_RegExpExec(ctx, splitter, str);
    if (LEPUS_IsException(z)) goto exception;
    if (LEPUS_IsNull(z)) {
      q = string_advance_index(strp, q, unicodeMatching);
    } else {
      if (JS_ToLengthFree(ctx, &e,
                          JS_GetPropertyInternal_GC(
                              ctx, splitter, JS_ATOM_lastIndex, splitter, 0)))
        goto exception;
      if (e > size) e = size;
      if (e == p) {
        q = string_advance_index(strp, q, unicodeMatching);
      } else {
        sub = js_sub_string(ctx, strp, p, q);
        if (LEPUS_IsException(sub)) goto exception;
        if (JS_SetPropertyInt64_GC(ctx, A, lengthA++, sub) < 0) goto exception;
        if (lengthA == lim) goto done;
        p = e;
        if (js_get_length64(ctx, &numberOfCaptures, z)) goto exception;
        for (i = 1; i < numberOfCaptures; i++) {
          sub = JS_GetPropertyInt64(ctx, z, i);
          if (LEPUS_IsException(sub)) goto exception;
          if (JS_SetPropertyInt64_GC(ctx, A, lengthA++, sub) < 0)
            goto exception;
          if (lengthA == lim) goto done;
        }
        q = p;
      }
    }
  }
add_tail:
  if (p > size) p = size;
  sub = js_sub_string(ctx, strp, p, size);
  if (LEPUS_IsException(sub)) goto exception;
  if (JS_SetPropertyInt64_GC(ctx, A, lengthA++, sub) < 0) goto exception;
  goto done;
exception:
  A = LEPUS_EXCEPTION;
done:
  return A;
}

static const LEPUSCFunctionListEntry js_regexp_funcs[] = {
    LEPUS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL),
    // LEPUS_CFUNC_DEF("__RegExpExec", 2, js_regexp___RegExpExec ),
    // LEPUS_CFUNC_DEF("__RegExpDelete", 2, js_regexp___RegExpDelete ),
};

static const LEPUSCFunctionListEntry js_regexp_proto_funcs[] = {
    LEPUS_CGETSET_DEF("flags", js_regexp_get_flags, NULL),
    LEPUS_CGETSET_DEF("source", js_regexp_get_source, NULL),
    LEPUS_CGETSET_MAGIC_DEF("global", js_regexp_get_flag, NULL, 1),
    LEPUS_CGETSET_MAGIC_DEF("ignoreCase", js_regexp_get_flag, NULL, 2),
    LEPUS_CGETSET_MAGIC_DEF("multiline", js_regexp_get_flag, NULL, 4),
    LEPUS_CGETSET_MAGIC_DEF("dotAll", js_regexp_get_flag, NULL, 8),
    LEPUS_CGETSET_MAGIC_DEF("unicode", js_regexp_get_flag, NULL, 16),
    LEPUS_CGETSET_MAGIC_DEF("sticky", js_regexp_get_flag, NULL, 32),
    LEPUS_CFUNC_DEF("exec", 1, js_regexp_exec),
    LEPUS_CFUNC_DEF("compile", 2, js_regexp_compile),
    LEPUS_CFUNC_DEF("test", 1, js_regexp_test),
    LEPUS_CFUNC_DEF("toString", 0, js_regexp_toString),
    LEPUS_CFUNC_DEF("[Symbol.replace]", 2, js_regexp_Symbol_replace),
    LEPUS_CFUNC_DEF("[Symbol.match]", 1, js_regexp_Symbol_match),
    LEPUS_CFUNC_DEF("[Symbol.matchAll]", 1, js_regexp_Symbol_matchAll),
    LEPUS_CFUNC_DEF("[Symbol.search]", 1, js_regexp_Symbol_search),
    LEPUS_CFUNC_DEF("[Symbol.split]", 2, js_regexp_Symbol_split),
    // LEPUS_CGETSET_DEF("__source", js_regexp_get___source, NULL ),
    // LEPUS_CGETSET_DEF("__flags", js_regexp_get___flags, NULL ),
};

static const LEPUSCFunctionListEntry js_regexp_string_iterator_proto_funcs[] = {
    LEPUS_ITERATOR_NEXT_DEF("next", 0, js_regexp_string_iterator_next, 0),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "RegExp String Iterator",
                          LEPUS_PROP_CONFIGURABLE),
};

void JS_AddIntrinsicRegExpCompiler_GC(LEPUSContext *ctx) {
  ctx->compile_regexp = js_compile_regexp;
}

void JS_AddIntrinsicRegExp_GC(LEPUSContext *ctx) {
  JS_AddIntrinsicRegExpCompiler_GC(ctx);

  ctx->regexp_ctor = JS_NewCConstructor(
      ctx, JS_CLASS_REGEXP, "RegExp", js_regexp_constructor, 2,
      LEPUS_CFUNC_constructor_or_func, 0, LEPUS_UNDEFINED, js_regexp_funcs,
      countof(js_regexp_funcs), js_regexp_proto_funcs,
      countof(js_regexp_proto_funcs), 0);

#if defined(__WASI_SDK__) || defined(QJS_UNITTEST)
  js_clear_regexp_caputre_property(ctx, ctx->regexp_ctor, 0);
#endif
  ctx->class_proto[JS_CLASS_REGEXP_STRING_ITERATOR] = JS_NewObjectProtoList(
      ctx, ctx->iterator_proto, js_regexp_string_iterator_proto_funcs,
      countof(js_regexp_string_iterator_proto_funcs));
}

#define JSON_TYPE_MASK ((uint8_t)0x07)
#define JSON_SUBTYPE_MASK ((uint8_t)0x18)
#define JSON_TAG_MASK ((uint8_t)0xFF)
#define JSON_TAG_BIT ((uint8_t)8)
#define JSON_TYPE_BIT ((uint8_t)3)

#define JSON_TYPE_NULL ((uint8_t)2)
#define JSON_TYPE_BOOL ((uint8_t)3)
#define JSON_TYPE_NUM ((uint8_t)4)
#define JSON_TYPE_STR ((uint8_t)5)
#define JSON_TYPE_ARR ((uint8_t)6)
#define JSON_TYPE_OBJ ((uint8_t)7)

#define JSON_SUBTYPE_FALSE ((uint8_t)(0 << 3)) /* ___00___ */
#define JSON_SUBTYPE_TRUE ((uint8_t)(1 << 3))  /* ___01___ */
#define JSON_SUBTYPE_SINT ((uint8_t)(1 << 3))  /* ___01___ */
#define JSON_SUBTYPE_REAL ((uint8_t)(2 << 3))  /* ___10___ */

#define USIZE_MAX ((size_t)(~(size_t)0))
#define U64_MAX U64(0xFFFFFFFF, 0xFFFFFFFF)

typedef struct v32 {
  char c1, c2, c3, c4;
} v32;

typedef struct v64 {
  char c1, c2, c3, c4, c5, c6, c7, c8;
} v64;

/* JSON */
#ifndef NO_QUICKJS_COMPILER
/* XXX: this parser is less strict than the JSON standard because we
   reuse the Javascript tokenizer. It could be improved by adding a
   specific JSON parse flag. */
#define JSON_READER_ESTIMATED_MINIFY_RATIO 6

static LEPUSValue json_parse_value(JSParseState *s) {
  LEPUSContext *ctx = s->ctx;
  LEPUSValue val = LEPUS_NULL;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  BOOL is_neg;
  int ret;

  switch (s->token.val) {
    case '{': {
      HandleScope block_scope(ctx->rt);
      LEPUSValue prop_val = LEPUS_UNDEFINED, prop_str = LEPUS_UNDEFINED;
      block_scope.PushHandle(&prop_val, HANDLE_TYPE_LEPUS_VALUE);
      block_scope.PushHandle(&prop_str, HANDLE_TYPE_LEPUS_VALUE);

      if (next_token(s)) goto fail;
      val = JS_NewObject_GC(ctx);
      if (LEPUS_IsException(val)) goto fail;
      if (s->token.val != '}') {
        for (;;) {
          if (s->token.val != TOK_STRING) {
            js_parse_error(s, "expecting property name");
            goto fail;
          }
          prop_str = s->token.u.str.str;
          if (next_token(s)) {
            goto fail;
          }
          if (js_parse_expect(s, ':')) {
            goto fail;
          }
          prop_val = json_parse_value(s);
          if (LEPUS_IsException(prop_val)) {
            goto fail;
          }
          ret = JS_DefinePropertyValueValue_GC(ctx, val, prop_str, prop_val,
                                               LEPUS_PROP_C_W_E);
          if (ret < 0) goto fail;

          if (s->token.val != ',') break;
          if (next_token(s)) goto fail;
        }
      }
      if (js_parse_expect(s, '}')) goto fail;
    } break;
    case '[': {
      HandleScope block_scope(ctx->rt);
      LEPUSValue el = LEPUS_UNDEFINED;
      block_scope.PushHandle(&el, HANDLE_TYPE_LEPUS_VALUE);
      uint32_t idx;

      if (next_token(s)) goto fail;
      val = JS_NewArray_GC(ctx);
      if (LEPUS_IsException(val)) goto fail;
      if (s->token.val != ']') {
        idx = 0;
        for (;;) {
          el = json_parse_value(s);
          if (LEPUS_IsException(el)) goto fail;
          ret = JS_DefinePropertyValueUint32_GC(ctx, val, idx, el,
                                                LEPUS_PROP_C_W_E);
          if (ret < 0) goto fail;
          if (s->token.val != ',') break;
          if (next_token(s)) goto fail;
          idx++;
        }
      }
      if (js_parse_expect(s, ']')) goto fail;
    } break;
    case TOK_STRING:
      val = s->token.u.str.str;
      if (next_token(s)) goto fail;
      break;
    case TOK_NUMBER:
      is_neg = 0;
      goto number;
    case '-':
      if (next_token(s)) goto fail;
      if (s->token.val != TOK_NUMBER) {
        js_parse_error(s, "number expected");
        goto fail;
      }
      is_neg = 1;
    number:
      val = s->token.u.num.val;
      if (is_neg) {
        double d;
        JS_ToFloat64_GC(ctx, &d, val); /* no exception possible */
        val = LEPUS_NewFloat64(ctx, -d);
      }
      if (next_token(s)) goto fail;
      break;
    case TOK_FALSE:
    case TOK_TRUE:
      val = LEPUS_NewBool(ctx, s->token.val - TOK_FALSE);
      if (next_token(s)) goto fail;
      break;
    case TOK_NULL:
      if (next_token(s)) goto fail;
      break;
    default:
      if (s->token.val == TOK_EOF) {
        js_parse_error(s, "unexpected end of input");
      } else {
        js_parse_error(s, "unexpected token: '%.*s'",
                       static_cast<int>(s->buf_ptr - s->token.ptr),
                       s->token.ptr);
      }
      goto fail;
  }
  return val;
fail:
  return LEPUS_EXCEPTION;
}
#endif

static LEPUSValue internalize_json_property(LEPUSContext *ctx,
                                            LEPUSValueConst holder, JSAtom name,
                                            LEPUSValueConst reviver) {
  LEPUSValue val, new_el = LEPUS_UNDEFINED, name_val, res;
  HandleScope func_scope(ctx, &new_el, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst args[2];
  func_scope.PushLEPUSValueArrayHandle(args, 2);
  int ret, is_array;
  uint32_t i, len = 0;
  JSAtom prop;
  LEPUSPropertyEnum *atoms = NULL;
  func_scope.PushHandle(&atoms, HANDLE_TYPE_HEAP_OBJ);

  if (js_check_stack_overflow(ctx, 0)) {
    return JS_ThrowStackOverflow_GC(ctx);
  }

  val = JS_GetPropertyInternal_GC(ctx, holder, name, holder, 0);
  if (LEPUS_IsException(val)) return val;
  if (LEPUS_IsObject(val)) {
    is_array = JS_IsArray_GC(ctx, val);
    if (is_array < 0) goto fail;
    if (is_array) {
      if (js_get_length32_gc(ctx, &len, val)) goto fail;
    } else {
      ret = JS_GetOwnPropertyNamesInternal(
          ctx, &atoms, &len, LEPUS_VALUE_GET_OBJ(val),
          LEPUS_GPN_ENUM_ONLY | LEPUS_GPN_STRING_MASK);
      if (ret < 0) goto fail;
    }
    for (i = 0; i < len; i++) {
      if (is_array) {
        prop = JS_NewAtomUInt32_GC(ctx, i);
        if (prop == JS_ATOM_NULL) goto fail;
      } else {
        prop = atoms[i].atom;
      }
      func_scope.PushLEPUSAtom(prop);
      new_el = internalize_json_property(ctx, val, prop, reviver);
      if (LEPUS_IsException(new_el)) {
        goto fail;
      }
      if (LEPUS_IsUndefined(new_el)) {
        ret = JS_DeleteProperty_GC(ctx, val, prop, 0);
      } else {
        ret =
            JS_DefinePropertyValue_GC(ctx, val, prop, new_el, LEPUS_PROP_C_W_E);
      }
      if (ret < 0) goto fail;
    }
  }
  atoms = NULL;
  name_val = JS_AtomToValue_GC(ctx, name);
  if (LEPUS_IsException(name_val)) goto fail;
  args[0] = name_val;
  args[1] = val;
  res = JS_Call_GC(ctx, reviver, holder, 2, args);
  return res;
fail:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_json_parse(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv) {
  LEPUSValue obj = LEPUS_UNDEFINED, root = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&root, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst reviver = LEPUS_UNDEFINED;
  func_scope.PushHandle(&reviver, HANDLE_TYPE_LEPUS_VALUE);
  const char *str;
  size_t len;

  str = JS_ToCStringLen2_GC(ctx, &len, argv[0], 0);
  if (!str) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&str, HANDLE_TYPE_CSTRING);
  obj = JS_ParseJSONOPT(ctx, str, len, "<input>");
  if (LEPUS_IsException(obj)) return obj;
  if (argc > 1 && LEPUS_IsFunction(ctx, argv[1])) {
    reviver = argv[1];
    root = JS_NewObject_GC(ctx);
    if (LEPUS_IsException(root)) {
      return LEPUS_EXCEPTION;
    }
    if (JS_DefinePropertyValue_GC(ctx, root, JS_ATOM_empty_string, obj,
                                  LEPUS_PROP_C_W_E) < 0) {
      return LEPUS_EXCEPTION;
    }
    obj = internalize_json_property(ctx, root, JS_ATOM_empty_string, reviver);
  }
  return obj;
}

typedef struct JSONStringifyContext {
  LEPUSValueConst replacer_func;
  LEPUSValue stack;
  LEPUSValue property_list;
  LEPUSValue gap;
  LEPUSValue empty;
  StringBuffer *b;
} JSONStringifyContext;

static LEPUSValue JS_ToQuotedStringFree(LEPUSContext *ctx, LEPUSValue val) {
  LEPUSValue r = JS_ToQuotedString(ctx, val);
  return r;
}

static LEPUSValue js_json_check(LEPUSContext *ctx, JSONStringifyContext *jsc,
                                LEPUSValueConst holder, LEPUSValue val,
                                LEPUSValueConst key) {
  LEPUSValue v = LEPUS_UNDEFINED;

  if (LEPUS_IsObject(val) || JS_IsBigInt(ctx, val) /* XXX: probably useless */
  ) {
    LEPUSValue f = JS_GetPropertyInternal_GC(ctx, val, JS_ATOM_toJSON, val, 0);
    if (LEPUS_IsException(f)) goto exception;
    if (LEPUS_IsFunction(ctx, f)) {
      v = JS_CallFree_GC(ctx, f, val, 1, &key);
      val = v;
      if (LEPUS_IsException(val)) goto exception;
    }
  }

  LEPUSValueConst args[2];
  if (!LEPUS_IsUndefined(jsc->replacer_func)) {
    args[0] = key;
    args[1] = val;
    v = JS_Call_GC(ctx, jsc->replacer_func, holder, 2, args);
    val = v;
    if (LEPUS_IsException(val)) goto exception;
  }

  switch (LEPUS_VALUE_GET_NORM_TAG(val)) {
    case LEPUS_TAG_OBJECT:
      if (LEPUS_IsFunction(ctx, val)) break;
    case LEPUS_TAG_STRING:
    case LEPUS_TAG_INT:
    case LEPUS_TAG_FLOAT64:
    case LEPUS_TAG_BOOL:
    case LEPUS_TAG_NULL:
    case LEPUS_TAG_BIG_INT:
    case LEPUS_TAG_EXCEPTION:
    case LEPUS_TAG_SEPARABLE_STRING:
    case LEPUS_TAG_LEPUS_REF:
      return val;
    default:
      break;
  }
  return LEPUS_UNDEFINED;

exception:
  return LEPUS_EXCEPTION;
}

#define JSON_ALLOC_INIT_SIZE 256

// LEPUSValue --> json_val
static int make_json_val(LEPUSContext *ctx, LEPUSValue obj,
                         JSONStringifyContext *jsc, json_val *&val_hdr,
                         json_val *&val, size_t &alc_len, const char ***str_arr,
                         size_t &ts, size_t &cs) {
#define val_incr()                                                            \
  do {                                                                        \
    val++;                                                                    \
    if (unlikely(val >= val_end)) {                                           \
      if (make_json_val_incr(ctx, alc_len, &val_hdr, &val, &ctn, &val_end)) { \
        LEPUS_ThrowInternalError(ctx, "out of memory in JSON.stringify");     \
        goto fail_alloc;                                                      \
      }                                                                       \
    }                                                                         \
  } while (false)

  int64_t len;
  json_val *val_end = val_hdr + (alc_len - 2);
  json_val *ctn;
  LEPUSValue v = LEPUS_UNDEFINED, tab = LEPUS_UNDEFINED, prop = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &v, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&tab, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&prop, HANDLE_TYPE_LEPUS_VALUE);

  tab = LEPUS_UNDEFINED;
  prop = LEPUS_UNDEFINED;

  LEPUSObject *p;
  int cl, ret, is_lepus_array = 0;
  int64_t i;
  switch (LEPUS_VALUE_GET_NORM_TAG(obj)) {
    case LEPUS_TAG_LEPUS_REF: {
      if (JS_LepusRefIsTable(ctx->rt, obj)) {
        goto do_object_stringify;
      } else if (JS_LepusRefIsArray(ctx->rt, obj)) {
        is_lepus_array = 1;
        goto do_object_stringify;
      }
      goto process_null;
    } break;
    case LEPUS_TAG_OBJECT: {
      p = LEPUS_VALUE_GET_OBJ(obj);
      cl = p->class_id;
      if (cl == JS_CLASS_STRING) {
        obj = JS_ToStringFree(ctx, obj);
        if (LEPUS_IsException(obj)) goto exception;
        return make_json_val(ctx, obj, jsc, val_hdr, val, alc_len, str_arr, ts,
                             cs);
      } else if (cl == JS_CLASS_NUMBER) {
        obj = JS_ToNumberFree(ctx, obj);
        if (LEPUS_IsException(obj)) goto exception;
        return make_json_val(ctx, obj, jsc, val_hdr, val, alc_len, str_arr, ts,
                             cs);
      } else if (cl == JS_CLASS_BOOLEAN) {
        val_incr();
        if (JS_ToBool_GC(ctx, p->u.object_data)) {
          val->tag = JSON_TYPE_BOOL | JSON_SUBTYPE_TRUE;
        } else {
          val->tag = JSON_TYPE_BOOL | JSON_SUBTYPE_FALSE;
        }
        return 0;
      } else if (cl == JS_CLASS_BIG_INT) {
        LEPUS_ThrowTypeError(ctx, "Do not know how to serialize a BigInt");
        goto exception;
      }
    do_object_stringify:
      // check circular reference
      v = js_array_includes(ctx, jsc->stack, 1,
                            reinterpret_cast<LEPUSValueConst *>(&obj));
      if (LEPUS_IsException(v)) goto exception;
      if (unlikely(JS_ToBoolFree_GC(ctx, v))) {
        LEPUS_ThrowTypeError(ctx, "circular reference");
        goto exception;
      }
      v = js_array_push(ctx, jsc->stack, 1,
                        reinterpret_cast<LEPUSValueConst *>(&obj), 0);
      if (check_exception_free(ctx, v)) goto exception;

      ret = JS_IsArray_GC(ctx, obj) || is_lepus_array;
      if (unlikely(ret < 0)) goto exception;
      if (ret) {
        if (unlikely(js_get_length64(ctx, &len, obj))) goto exception;
        val_incr();
        size_t dis = (size_t)(val - val_hdr);
        for (i = 0; i < len; i++) {
          v = JS_GetPropertyInt64(ctx, obj, i);
          if (LEPUS_IsException(v)) goto exception;
          prop = JS_ToStringFree(ctx, JS_NewInt64_GC(ctx, i));
          if (LEPUS_IsException(prop)) goto exception;
          v = js_json_check(ctx, jsc, obj, v, prop);
          prop = LEPUS_UNDEFINED;
          if (LEPUS_IsException(v)) goto exception;
          if (unlikely(LEPUS_IsUndefined(v))) v = LEPUS_NULL;
          if (unlikely(make_json_val(ctx, v, jsc, val_hdr, val, alc_len,
                                     str_arr, ts, cs))) {
            len = i;
            ctn = val_hdr + dis;
            ctn->tag = ((len) << JSON_TAG_BIT) | JSON_TYPE_ARR;
            ctn->uni.ofs = (size_t)(reinterpret_cast<uint8_t *>(val) -
                                    reinterpret_cast<uint8_t *>(ctn)) +
                           sizeof(json_val);
            goto exception;
          }
        }
        ctn = val_hdr + dis;
        ctn->tag = ((len) << JSON_TAG_BIT) | JSON_TYPE_ARR;
        ctn->uni.ofs = (size_t)(reinterpret_cast<uint8_t *>(val) -
                                reinterpret_cast<uint8_t *>(ctn)) +
                       sizeof(json_val);
      } else {
        if (!LEPUS_IsUndefined(jsc->property_list)) {
          tab = jsc->property_list;
        } else {
          tab = js_object_keys(ctx, LEPUS_UNDEFINED, 1,
                               reinterpret_cast<LEPUSValueConst *>(&obj),
                               JS_ITERATOR_KIND_KEY);
        }
        if (LEPUS_IsException(tab)) goto exception;
        if (unlikely(js_get_length64(ctx, &len, tab))) goto exception;
        val_incr();
        size_t dis = (size_t)(val - val_hdr);
        size_t len_final = len;
        for (i = 0; i < len; i++) {
          prop = JS_GetPropertyInt64(ctx, tab, i);
          if (LEPUS_IsException(prop)) goto exception;

          v = JS_GetPropertyValue_GC(ctx, obj, prop);
          if (LEPUS_IsException(v)) goto exception;
          v = js_json_check(ctx, jsc, obj, v, prop);
          if (LEPUS_IsException(v)) goto exception;
          if (!LEPUS_IsUndefined(v)) {
            size_t str_len = 0;
            const char *str =
                generate_json_str(ctx, prop, str_len, str_arr, ts, cs);
            if (unlikely(!str)) {
              LEPUS_ThrowInternalError(ctx, "out of memory in JSON.stringify");
              goto fail_alloc;
            }
            val++;
            val->tag = (str_len << JSON_TAG_BIT) | JSON_TYPE_STR;
            val->uni.str = str;

            if (unlikely(make_json_val(ctx, v, jsc, val_hdr, val, alc_len,
                                       str_arr, ts, cs))) {
              goto obj_exception;
            }
            continue;
          obj_exception:
            len_final = i;
            ctn = val_hdr + dis;
            ctn->tag = (len_final << (JSON_TAG_BIT)) | JSON_TYPE_OBJ;
            ctn->uni.ofs = (size_t)(reinterpret_cast<uint8_t *>(val) -
                                    reinterpret_cast<uint8_t *>(ctn)) +
                           sizeof(json_val);
            goto exception;
          } else {
            len_final--;
          }
        }
        ctn = val_hdr + dis;
        ctn->tag = (len_final << (JSON_TAG_BIT)) | JSON_TYPE_OBJ;
        ctn->uni.ofs = (size_t)(reinterpret_cast<uint8_t *>(val) -
                                reinterpret_cast<uint8_t *>(ctn)) +
                       sizeof(json_val);
      }
      if (check_exception_free(ctx, js_array_pop(ctx, jsc->stack, 0, NULL, 0)))
        goto exception;
      return 0;
    }
    case LEPUS_TAG_STRING:
    case LEPUS_TAG_SEPARABLE_STRING: {
      size_t str_len = 0;
      const char *str = generate_json_str(ctx, obj, str_len, str_arr, ts, cs);
      if (unlikely(!str)) {
        LEPUS_ThrowInternalError(ctx, "out of memory in JSON.stringify");
        goto fail_alloc;
      }
      val_incr();
      val->tag = (str_len << JSON_TAG_BIT) | JSON_TYPE_STR;
      val->uni.str = str;
      return 0;
    }
    case LEPUS_TAG_FLOAT64:
      if (unlikely(!isfinite(LEPUS_VALUE_GET_FLOAT64(obj)))) {
        obj = LEPUS_NULL;
        goto process_null;
      }
      double d;
      JS_ToFloat64_GC(ctx, &d, obj);
      if (unlikely(abs(d) <= 1e-15)) {
        goto process_int;
      }
      goto process_float;
    process_float:
      val_incr();
      val->tag = JSON_TYPE_NUM | JSON_SUBTYPE_REAL;
      val->uni.f64 = d;
      return 0;
    case LEPUS_TAG_INT: {
    process_int:
      val_incr();
      int64_t num = 0;
      JS_ToInt64_GC(ctx, &num, obj);
      val->tag = JSON_TYPE_NUM | JSON_SUBTYPE_SINT;
      val->uni.i64 = num;
      return 0;
    }
    case LEPUS_TAG_BIG_INT: {
// Special handling for INT64-bit data in lynx
#if JS_LIMB_BITS == 64
      auto *p = LEPUS_VALUE_GET_BIGINT(obj);
      if (p->len == 1) {
        val_incr();
        int64_t num = p->tab[0];
        val->tag = JSON_TYPE_NUM | JSON_SUBTYPE_SINT;
        val->uni.i64 = num;
        return 0;
      } else if (p->len == 2 && p->tab[1] == 0) {
        val_incr();
        uint64_t num = p->tab[0];
        val->tag = JSON_TYPE_NUM | JSON_SUBTYPE_REAL;
        val->uni.f64 = static_cast<double>(num);
        return 0;
      }
#endif
      LEPUS_ThrowTypeError(ctx, "bigint are forbidden in JSON.stringify");
      goto exception;
    }
    case LEPUS_TAG_BOOL: {
      val_incr();
      if (JS_ToBool_GC(ctx, obj)) {
        val->tag = JSON_TYPE_BOOL | JSON_SUBTYPE_TRUE;
      } else {
        val->tag = JSON_TYPE_BOOL | JSON_SUBTYPE_FALSE;
      }
      return 0;
    }
    case LEPUS_TAG_NULL: {
    process_null:
      val_incr();
      val->tag = JSON_TYPE_NULL;
      return 0;
    }
    default: {
      return 0;
    }
  }
exception:
fail_alloc:
  return -1;
#undef val_incr
}

QJS_HIDE LEPUSValue js_json_stringify_opt_GC(LEPUSContext *ctx,
                                             LEPUSValueConst this_val, int argc,
                                             LEPUSValueConst *argv) {
  // stringify(val, replacer, space)
  JSONStringifyContext jsc_s, *jsc = &jsc_s;
  LEPUSValueConst replacer = argv[1];
  LEPUSValue val = LEPUS_UNDEFINED, v = LEPUS_UNDEFINED,
             space = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&v, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&space, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue ret, wrapper;
  int res;
  int64_t i, j, n;

  json_val *json_cur_val = nullptr;
  json_val *json_root = nullptr;
  uint8_t *json_str = nullptr;
  const char *gap_str = nullptr;
  size_t alc_len = JSON_ALLOC_INIT_SIZE;
  // need to free in the end of JSON.stringify
  const char **str_arr = (const char **)lepus_mallocz(
      ctx, sizeof(char *) * JSON_ALLOC_INIT_SIZE, ALLOC_TAG_JsonStrArray);
  if (!str_arr)
    return LEPUS_ThrowInternalError(ctx, "out of memory in JSON.stringify");
  func_scope.PushHandle(&str_arr, HANDLE_TYPE_HEAP_OBJ);
  size_t ts = JSON_ALLOC_INIT_SIZE;
  size_t cs = 0;
  set_heap_obj_len(str_arr, 0);

  jsc->replacer_func = LEPUS_UNDEFINED;
  jsc->stack = LEPUS_UNDEFINED;
  jsc->property_list = LEPUS_UNDEFINED;
  jsc->gap = LEPUS_UNDEFINED;
  jsc->empty = JS_AtomToString_GC(ctx, JS_ATOM_empty_string);
  func_scope.PushHandle(&jsc->replacer_func, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&jsc->stack, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&jsc->property_list, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&jsc->gap, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&jsc->empty, HANDLE_TYPE_LEPUS_VALUE);

  ret = LEPUS_UNDEFINED;
  wrapper = LEPUS_UNDEFINED;
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&wrapper, HANDLE_TYPE_LEPUS_VALUE);

  jsc->stack = JS_NewArray_GC(ctx);
  if (LEPUS_IsException(jsc->stack)) goto exception;
  if (LEPUS_IsFunction(ctx, replacer)) {
    jsc->replacer_func = replacer;
  } else {
    res = JS_IsArray_GC(ctx, replacer);
    if (res < 0) goto exception;
    if (res == 0 && LEPUS_IsLepusRef(replacer) && ctx) {
      res = JS_LepusRefIsArray(ctx->rt, replacer);
    }
    if (res) {
      /* XXX: enumeration is not fully correct */
      jsc->property_list = JS_NewArray_GC(ctx);
      if (LEPUS_IsException(jsc->property_list)) goto exception;
      if (js_get_length64(ctx, &n, replacer)) goto exception;
      for (i = j = 0; i < n; i++) {
        LEPUSValue present;
        v = JS_GetPropertyInt64(ctx, replacer, i);
        if (LEPUS_IsException(v)) goto exception;
        if (LEPUS_IsObject(v)) {
          LEPUSObject *p = LEPUS_VALUE_GET_OBJ(v);
          if (p->class_id == JS_CLASS_STRING ||
              p->class_id == JS_CLASS_NUMBER) {
            v = JS_ToStringFree(ctx, v);
            if (LEPUS_IsException(v)) goto exception;
          } else {
            continue;
          }
        } else if (LEPUS_IsNumber(v)) {
          v = JS_ToStringFree(ctx, v);
          if (LEPUS_IsException(v)) goto exception;
        } else if (!LEPUS_IsString(v)) {
          continue;
        }
        present = js_array_includes(ctx, jsc->property_list, 1,
                                    reinterpret_cast<LEPUSValueConst *>(&v));
        if (LEPUS_IsException(present)) {
          goto exception;
        }
        if (!JS_ToBoolFree_GC(ctx, present)) {
          JS_SetPropertyInt64_GC(ctx, jsc->property_list, j++, v);
        }
      }
    }
  }
  space = argv[2];
  if (LEPUS_IsObject(space)) {
    LEPUSObject *p = LEPUS_VALUE_GET_OBJ(space);
    if (p->class_id == JS_CLASS_NUMBER) {
      space = JS_ToNumberFree(ctx, space);
    } else if (p->class_id == JS_CLASS_STRING) {
      space = JS_ToStringFree(ctx, space);
    }
    if (LEPUS_IsException(space)) {
      goto exception;
    }
  }
  if (LEPUS_IsNumber(space)) {
    int n;
    if (JS_ToInt32Clamp(ctx, &n, space, 0, 10, 0)) goto exception;
    jsc->gap = JS_NewStringLen_GC(ctx, "          ", n);
  } else if (LEPUS_IsString(space)) {
    JSString *p = LEPUS_VALUE_GET_STRING(space);
    jsc->gap = js_sub_string(ctx, p, 0, min_int(p->len, 10));
  } else {
    jsc->gap = jsc->empty;
  }
  if (LEPUS_IsException(jsc->gap)) goto exception;
  wrapper = JS_NewObject_GC(ctx);
  if (LEPUS_IsException(wrapper)) goto exception;
  if (JS_DefinePropertyValue_GC(ctx, wrapper, JS_ATOM_empty_string, argv[0],
                                LEPUS_PROP_C_W_E) < 0) {
    goto exception;
  }
  val = argv[0];
  val = js_json_check(ctx, jsc, wrapper, val, jsc->empty);
  if (LEPUS_IsException(val)) goto exception;
  if (LEPUS_IsUndefined(val)) {
    ret = LEPUS_UNDEFINED;
    goto done;
  }

  json_root = static_cast<json_val *>(
      lepus_mallocz(ctx, alc_len * sizeof(json_val), ALLOC_TAG_WITHOUT_PTR));
  if (unlikely(!json_root)) {
    LEPUS_ThrowInternalError(ctx, "out of memory in JSON.stringify");
    goto exception;
  }
  func_scope.PushHandle(&json_root, HANDLE_TYPE_HEAP_OBJ);
  json_cur_val = json_root;
  if (make_json_val(ctx, val, jsc, json_root, json_cur_val, alc_len, &str_arr,
                    ts, cs)) {
    goto exception;
  }
  gap_str = JS_ToCStringLen2_GC(ctx, NULL, jsc->gap, 0);
  func_scope.PushHandle(&gap_str, HANDLE_TYPE_CSTRING);
  if (gap_str && *gap_str != '\0') {
    json_str = json_val_write_format(ctx, json_root + 1, gap_str);
  } else {
    json_str = json_val_write(ctx, json_root + 1);
  }
  func_scope.PushHandle(json_str, HANDLE_TYPE_DIR_HEAP_OBJ);
  ret = json_str ? JS_NewString_GC(ctx, reinterpret_cast<char *>(json_str))
                 : LEPUS_EXCEPTION;
  goto done;

exception:
  ret = LEPUS_EXCEPTION;
done:
  return ret;
}
/*
static const LEPUSCFunctionListEntry js_json_funcs[] = {
    LEPUS_CFUNC_DEF("parse", 2, js_json_parse),
    LEPUS_CFUNC_DEF("stringify", 3, js_json_stringify),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "JSON",
                          LEPUS_PROP_CONFIGURABLE),
};
*/

static const LEPUSCFunctionListEntry js_json_funcs_opt[] = {
    LEPUS_CFUNC_DEF("parse", 2, js_json_parse),
    LEPUS_CFUNC_DEF("stringify", 3, js_json_stringify_opt_GC),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "JSON",
                          LEPUS_PROP_CONFIGURABLE),
};

/*static const LEPUSCFunctionListEntry js_json_obj[] = {
    LEPUS_OBJECT_DEF("JSON", js_json_funcs, countof(js_json_funcs),
                     LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE),
};*/

static const LEPUSCFunctionListEntry js_json_obj_opt[] = {
    LEPUS_OBJECT_DEF("JSON", js_json_funcs_opt, countof(js_json_funcs_opt),
                     LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE),
};

void JS_AddIntrinsicJSON_GC(LEPUSContext *ctx) {
  /* add JSON as autoinit object */
  // if (!json_opt_disabled(ctx->rt)) {
  JS_SetPropertyFunctionList_GC(ctx, ctx->global_obj, js_json_obj_opt,
                                countof(js_json_obj_opt));
  //} else {
  //  JS_SetPropertyFunctionList_GC(ctx, ctx->global_obj, js_json_obj,
  //                                   countof(js_json_obj));
  //}
}

/* Reflect */

static LEPUSValue js_reflect_apply(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
  return js_function_apply_gc(ctx, argv[0], max_int(0, argc - 1), argv + 1, 0);
}

static LEPUSValue js_reflect_construct(LEPUSContext *ctx,
                                       LEPUSValueConst this_val, int argc,
                                       LEPUSValueConst *argv) {
  LEPUSValueConst func, array_arg, new_target;
  LEPUSValue *tab, ret;
  uint32_t len;

  func = argv[0];
  array_arg = argv[1];
  if (argc > 2) {
    new_target = argv[2];
    if (!LEPUS_IsConstructor(ctx, new_target))
      return LEPUS_ThrowTypeError(ctx, "not a constructor");
  } else {
    new_target = func;
  }
  tab = build_arg_list(ctx, &len, array_arg);
  if (!tab) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, tab, HANDLE_TYPE_DIR_HEAP_OBJ);
  ret = JS_CallConstructor2_GC(ctx, func, new_target, len,
                               reinterpret_cast<LEPUSValueConst *>(tab));
  return ret;
}

static LEPUSValue js_reflect_ownKeys(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv) {
  if (LEPUS_VALUE_IS_NOT_OBJECT(argv[0]))
    return JS_ThrowTypeErrorNotAnObject(ctx);
  return JS_GetOwnPropertyNames2(ctx, argv[0],
                                 LEPUS_GPN_STRING_MASK | LEPUS_GPN_SYMBOL_MASK,
                                 JS_ITERATOR_KIND_KEY);
}

static const LEPUSCFunctionListEntry js_reflect_funcs[] = {
    LEPUS_CFUNC_DEF("apply", 3, js_reflect_apply),
    LEPUS_CFUNC_DEF("construct", 2, js_reflect_construct),
    LEPUS_CFUNC_MAGIC_DEF("defineProperty", 3, js_object_defineProperty, 1),
    LEPUS_CFUNC_DEF("deleteProperty", 2, js_reflect_deleteProperty),
    LEPUS_CFUNC_DEF("get", 2, js_reflect_get),
    LEPUS_CFUNC_MAGIC_DEF("getOwnPropertyDescriptor", 2,
                          js_object_getOwnPropertyDescriptor_GC, 1),
    LEPUS_CFUNC_MAGIC_DEF("getPrototypeOf", 1, js_object_getPrototypeOf, 1),
    LEPUS_CFUNC_DEF("has", 2, js_reflect_has),
    LEPUS_CFUNC_MAGIC_DEF("isExtensible", 1, js_object_isExtensible, 1),
    LEPUS_CFUNC_DEF("ownKeys", 1, js_reflect_ownKeys),
    LEPUS_CFUNC_MAGIC_DEF("preventExtensions", 1, js_object_preventExtensions,
                          1),
    LEPUS_CFUNC_DEF("set", 3, js_reflect_set),
    LEPUS_CFUNC_DEF("setPrototypeOf", 2, js_reflect_setPrototypeOf),
};

static const LEPUSCFunctionListEntry js_reflect_obj[] = {
    LEPUS_OBJECT_DEF("Reflect", js_reflect_funcs, countof(js_reflect_funcs),
                     LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE),
};

/* Proxy */

static void js_proxy_finalizer(LEPUSRuntime *rt, LEPUSValue val) {}

static void js_proxy_mark(LEPUSRuntime *rt, LEPUSValueConst val,
                          LEPUS_MarkFunc *mark_func, uint64_t trace_tool) {
  JSProxyData *s =
      static_cast<JSProxyData *>(LEPUS_GetOpaque(val, JS_CLASS_PROXY));
  if (s) {
    JS_MarkValue_GC(rt, s->target, mark_func, trace_tool);
    JS_MarkValue_GC(rt, s->handler, mark_func, trace_tool);
    JS_MarkValue_GC(rt, s->proto, mark_func, trace_tool);
  }
}

static LEPUSValue JS_ThrowTypeErrorRevokedProxy(LEPUSContext *ctx) {
  return LEPUS_ThrowTypeError(ctx, "revoked proxy");
}

static JSProxyData *get_proxy_method(LEPUSContext *ctx, LEPUSValue *pmethod,
                                     LEPUSValueConst obj, JSAtom name) {
  JSProxyData *s =
      static_cast<JSProxyData *>(LEPUS_GetOpaque(obj, JS_CLASS_PROXY));
  LEPUSValue method;

  /* safer to test recursion in all proxy methods */
  if (js_check_stack_overflow(ctx, 0)) {
    JS_ThrowStackOverflow_GC(ctx);
    return NULL;
  }

  /* 's' should never be NULL */
  if (s->is_revoked) {
    JS_ThrowTypeErrorRevokedProxy(ctx);
    return NULL;
  }
  method = JS_GetPropertyInternal_GC(ctx, s->handler, name, s->handler, 0);
  if (LEPUS_IsException(method)) return NULL;
  if (LEPUS_IsNull(method)) method = LEPUS_UNDEFINED;
  *pmethod = method;
  return s;
}

static LEPUSValueConst js_proxy_getPrototypeOf(LEPUSContext *ctx,
                                               LEPUSValueConst obj) {
  JSProxyData *s;
  LEPUSValue method = LEPUS_UNDEFINED, ret;
  HandleScope func_scope(ctx, &method, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst proto1 = LEPUS_UNDEFINED;
  func_scope.PushHandle(&proto1, HANDLE_TYPE_LEPUS_VALUE);
  int res;

  /* must check for timeout to avoid infinite loop in instanceof */
  if (js_poll_interrupts(ctx)) return LEPUS_EXCEPTION;

  s = get_proxy_method(ctx, &method, obj, JS_ATOM_getPrototypeOf);
  if (!s) return LEPUS_EXCEPTION;
  if (LEPUS_IsUndefined(method)) return JS_GetPrototype_GC(ctx, s->target);
  ret = JS_CallFree_GC(ctx, method, s->handler, 1,
                       reinterpret_cast<LEPUSValueConst *>(&s->target));
  if (LEPUS_IsException(ret)) return ret;
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
  if (!LEPUS_VALUE_IS_NULL(ret) && LEPUS_VALUE_IS_NOT_OBJECT(ret)) {
    goto fail;
  }
  res = JS_IsExtensible_GC(ctx, s->target);
  if (res < 0) {
    return LEPUS_EXCEPTION;
  }
  if (!res) {
    /* check invariant */
    proto1 = JS_GetPrototype_GC(ctx, s->target);
    if (LEPUS_IsException(proto1)) {
      return LEPUS_EXCEPTION;
    }
    if (LEPUS_VALUE_GET_OBJ(proto1) != LEPUS_VALUE_GET_OBJ(ret)) {
    fail:
      return LEPUS_ThrowTypeError(ctx, "proxy: inconsistent prototype");
    }
  }
  /* store the prototype in the proxy so that its refcount is at least 1 */
  set_value_gc(ctx, &s->proto, ret);
  return (LEPUSValueConst)ret;
}

static int js_proxy_setPrototypeOf(LEPUSContext *ctx, LEPUSValueConst obj,
                                   LEPUSValueConst proto_val, BOOL throw_flag) {
  JSProxyData *s;
  LEPUSValue method = LEPUS_UNDEFINED, ret, proto1;
  HandleScope func_scope(ctx, &method, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst args[2];
  func_scope.PushLEPUSValueArrayHandle(args, 2);
  BOOL res;
  int res2;

  s = get_proxy_method(ctx, &method, obj, JS_ATOM_setPrototypeOf);
  if (!s) return -1;
  if (LEPUS_IsUndefined(method))
    return JS_SetPrototypeInternal_GC(ctx, s->target, proto_val, throw_flag);
  args[0] = s->target;
  args[1] = proto_val;
  ret = JS_CallFree_GC(ctx, method, s->handler, 2, args);
  if (LEPUS_IsException(ret)) return -1;
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
  res = JS_ToBoolFree_GC(ctx, ret);
  if (!res) {
    if (throw_flag) {
      LEPUS_ThrowTypeError(ctx, "proxy: bad prototype");
      return -1;
    } else {
      return FALSE;
    }
  }
  res2 = JS_IsExtensible_GC(ctx, s->target);
  if (res2 < 0) return -1;
  if (!res2) {
    proto1 = JS_GetPrototype_GC(ctx, s->target);
    if (LEPUS_IsException(proto1)) return -1;
    func_scope.PushHandle(&proto1, HANDLE_TYPE_LEPUS_VALUE);
    if (LEPUS_VALUE_GET_OBJ(proto_val) != LEPUS_VALUE_GET_OBJ(proto1)) {
      LEPUS_ThrowTypeError(ctx, "proxy: inconsistent prototype");
      return -1;
    }
  }
  return TRUE;
}

static int js_proxy_isExtensible(LEPUSContext *ctx, LEPUSValueConst obj) {
  JSProxyData *s;
  LEPUSValue method = LEPUS_UNDEFINED, ret;
  HandleScope func_scope(ctx, &method, HANDLE_TYPE_LEPUS_VALUE);
  BOOL res;
  int res2;

  s = get_proxy_method(ctx, &method, obj, JS_ATOM_isExtensible);
  if (!s) return -1;
  if (LEPUS_IsUndefined(method)) return JS_IsExtensible_GC(ctx, s->target);
  ret = JS_CallFree_GC(ctx, method, s->handler, 1,
                       reinterpret_cast<LEPUSValueConst *>(&s->target));
  if (LEPUS_IsException(ret)) return -1;
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
  res = JS_ToBoolFree_GC(ctx, ret);
  res2 = JS_IsExtensible_GC(ctx, s->target);
  if (res2 < 0) return res2;
  if (res != res2) {
    LEPUS_ThrowTypeError(ctx, "proxy: inconsistent isExtensible");
    return -1;
  }
  return res;
}

static int js_proxy_preventExtensions(LEPUSContext *ctx, LEPUSValueConst obj) {
  JSProxyData *s;
  LEPUSValue method = LEPUS_UNDEFINED, ret;
  HandleScope func_scope(ctx, &method, HANDLE_TYPE_LEPUS_VALUE);
  BOOL res;
  int res2;

  s = get_proxy_method(ctx, &method, obj, JS_ATOM_preventExtensions);
  if (!s) return -1;
  if (LEPUS_IsUndefined(method)) return JS_PreventExtensions_GC(ctx, s->target);
  ret = JS_CallFree_GC(ctx, method, s->handler, 1,
                       reinterpret_cast<LEPUSValueConst *>(&s->target));
  if (LEPUS_IsException(ret)) return -1;
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
  res = JS_ToBoolFree_GC(ctx, ret);
  if (res) {
    res2 = JS_IsExtensible_GC(ctx, s->target);
    if (res2 < 0) return res2;
    if (res2) {
      LEPUS_ThrowTypeError(ctx, "proxy: inconsistent preventExtensions");
      return -1;
    }
  }
  return res;
}

QJS_STATIC int js_proxy_has(LEPUSContext *ctx, LEPUSValueConst obj,
                            JSAtom atom) {
  JSProxyData *s;
  LEPUSValue method = LEPUS_UNDEFINED, ret1, atom_val;
  HandleScope func_scope(ctx, &method, HANDLE_TYPE_LEPUS_VALUE);
  int ret, res;
  LEPUSObject *p;
  LEPUSValueConst args[2];
  func_scope.PushLEPUSValueArrayHandle(args, 2);
  BOOL res2;

  s = get_proxy_method(ctx, &method, obj, JS_ATOM_has);
  if (!s) return -1;
  if (LEPUS_IsUndefined(method)) return JS_HasProperty_GC(ctx, s->target, atom);
  atom_val = JS_AtomToValue_GC(ctx, atom);
  if (LEPUS_IsException(atom_val)) {
    return -1;
  }
  func_scope.PushHandle(&atom_val, HANDLE_TYPE_LEPUS_VALUE);
  args[0] = s->target;
  args[1] = atom_val;
  ret1 = JS_CallFree_GC(ctx, method, s->handler, 2, args);
  if (LEPUS_IsException(ret1)) return -1;
  func_scope.PushHandle(&ret1, HANDLE_TYPE_LEPUS_VALUE);
  ret = JS_ToBoolFree_GC(ctx, ret1);
  if (!ret) {
    LEPUSPropertyDescriptor desc;
    p = LEPUS_VALUE_GET_OBJ(s->target);
    res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom);
    if (res < 0) return -1;
    if (res) {
      res2 = !(desc.flags & LEPUS_PROP_CONFIGURABLE);
      if (res2 || !p->extensible) {
        LEPUS_ThrowTypeError(ctx, "proxy: inconsistent has");
        return -1;
      }
    }
  }
  return ret;
}

static LEPUSValue js_proxy_get(LEPUSContext *ctx, LEPUSValueConst obj,
                               JSAtom atom, LEPUSValueConst receiver) {
  JSProxyData *s;
  LEPUSValue method = LEPUS_UNDEFINED, ret, atom_val;
  HandleScope func_scope(ctx, &method, HANDLE_TYPE_LEPUS_VALUE);
  int res;
  LEPUSValueConst args[3];
  func_scope.PushLEPUSValueArrayHandle(args, 3);
  LEPUSPropertyDescriptor desc;
  func_scope.PushLEPUSPropertyDescriptor(&desc);

  s = get_proxy_method(ctx, &method, obj, JS_ATOM_get);
  if (!s) return LEPUS_EXCEPTION;
  /* Note: recursion is possible thru the prototype of s->target */
  if (LEPUS_IsUndefined(method))
    return JS_GetPropertyInternal_GC(ctx, s->target, atom, receiver, FALSE);
  atom_val = JS_AtomToValue_GC(ctx, atom);
  if (LEPUS_IsException(atom_val)) {
    return LEPUS_EXCEPTION;
  }
  func_scope.PushHandle(&atom_val, HANDLE_TYPE_LEPUS_VALUE);
  args[0] = s->target;
  args[1] = atom_val;
  args[2] = receiver;
  ret = JS_CallFree_GC(ctx, method, s->handler, 3, args);
  if (LEPUS_IsException(ret)) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
  res = JS_GetOwnPropertyInternal(ctx, &desc, LEPUS_VALUE_GET_OBJ(s->target),
                                  atom);
  if (res < 0) return LEPUS_EXCEPTION;
  if (res) {
    if ((desc.flags & (LEPUS_PROP_GETSET | LEPUS_PROP_CONFIGURABLE |
                       LEPUS_PROP_WRITABLE)) == 0) {
      if (!js_same_value(ctx, desc.value, ret)) {
        goto fail;
      }
    } else if ((desc.flags & (LEPUS_PROP_GETSET | LEPUS_PROP_CONFIGURABLE)) ==
               LEPUS_PROP_GETSET) {
      if (LEPUS_IsUndefined(desc.getter) && !LEPUS_IsUndefined(ret)) {
      fail:
        return LEPUS_ThrowTypeError(ctx, "proxy: inconsistent get");
      }
    }
  }
  return ret;
}

static int js_proxy_set(LEPUSContext *ctx, LEPUSValueConst obj, JSAtom atom,
                        LEPUSValueConst value, LEPUSValueConst receiver,
                        int flags) {
  JSProxyData *s;
  LEPUSValue method = LEPUS_UNDEFINED, ret1, atom_val;
  HandleScope func_scope(ctx, &method, HANDLE_TYPE_LEPUS_VALUE);
  int ret, res;
  LEPUSValueConst args[4];
  func_scope.PushLEPUSValueArrayHandle(args, 4);

  s = get_proxy_method(ctx, &method, obj, JS_ATOM_set);
  if (!s) return -1;
  if (LEPUS_IsUndefined(method)) {
    return JS_SetPropertyGeneric_GC(ctx, LEPUS_VALUE_GET_OBJ(s->target), atom,
                                    value, receiver, flags);
  }
  atom_val = JS_AtomToValue_GC(ctx, atom);
  if (LEPUS_IsException(atom_val)) {
    return -1;
  }
  args[0] = s->target;
  args[1] = atom_val;
  args[2] = value;
  args[3] = receiver;
  ret1 = JS_CallFree_GC(ctx, method, s->handler, 4, args);
  if (LEPUS_IsException(ret1)) return -1;
  func_scope.PushHandle(&ret1, HANDLE_TYPE_LEPUS_VALUE);
  ret = JS_ToBoolFree_GC(ctx, ret1);
  if (ret) {
    HandleScope block_scope(ctx->rt);
    LEPUSPropertyDescriptor desc;
    ctx->ptr_handles->PushLEPUSPropertyDescriptor(&desc);
    res = JS_GetOwnPropertyInternal(ctx, &desc, LEPUS_VALUE_GET_OBJ(s->target),
                                    atom);
    if (res < 0) return -1;
    if (res) {
      if ((desc.flags & (LEPUS_PROP_GETSET | LEPUS_PROP_CONFIGURABLE |
                         LEPUS_PROP_WRITABLE)) == 0) {
        if (!js_same_value(ctx, desc.value, value)) {
          goto fail;
        }
      } else if ((desc.flags & (LEPUS_PROP_GETSET | LEPUS_PROP_CONFIGURABLE)) ==
                     LEPUS_PROP_GETSET &&
                 LEPUS_IsUndefined(desc.setter)) {
      fail:
        LEPUS_ThrowTypeError(ctx, "proxy: inconsistent set");
        return -1;
      }
    }
  } else {
    if ((flags & LEPUS_PROP_THROW) ||
        ((flags & LEPUS_PROP_THROW_STRICT) && is_strict_mode(ctx))) {
      LEPUS_ThrowTypeError(ctx, "proxy: cannot set property");
      return -1;
    }
  }
  return ret;
}

static LEPUSValue js_create_desc(LEPUSContext *ctx, LEPUSValueConst val,
                                 LEPUSValueConst getter, LEPUSValueConst setter,
                                 int flags) {
  LEPUSValue ret;
  ret = JS_NewObject_GC(ctx);
  if (LEPUS_IsException(ret)) return ret;
  HandleScope func_scope(ctx, &ret, HANDLE_TYPE_LEPUS_VALUE);
  if (flags & LEPUS_PROP_HAS_GET) {
    JS_DefinePropertyValue_GC(ctx, ret, JS_ATOM_get, getter, LEPUS_PROP_C_W_E);
  }
  if (flags & LEPUS_PROP_HAS_SET) {
    JS_DefinePropertyValue_GC(ctx, ret, JS_ATOM_set, setter, LEPUS_PROP_C_W_E);
  }
  if (flags & LEPUS_PROP_HAS_VALUE) {
    JS_DefinePropertyValue_GC(ctx, ret, JS_ATOM_value, val, LEPUS_PROP_C_W_E);
  }
  if (flags & LEPUS_PROP_HAS_WRITABLE) {
    JS_DefinePropertyValue_GC(
        ctx, ret, JS_ATOM_writable,
        LEPUS_NewBool(ctx, (flags & LEPUS_PROP_WRITABLE) != 0),
        LEPUS_PROP_C_W_E);
  }
  if (flags & LEPUS_PROP_HAS_ENUMERABLE) {
    JS_DefinePropertyValue_GC(
        ctx, ret, JS_ATOM_enumerable,
        LEPUS_NewBool(ctx, (flags & LEPUS_PROP_ENUMERABLE) != 0),
        LEPUS_PROP_C_W_E);
  }
  if (flags & LEPUS_PROP_HAS_CONFIGURABLE) {
    JS_DefinePropertyValue_GC(
        ctx, ret, JS_ATOM_configurable,
        LEPUS_NewBool(ctx, (flags & LEPUS_PROP_CONFIGURABLE) != 0),
        LEPUS_PROP_C_W_E);
  }
  return ret;
}

QJS_STATIC int js_proxy_get_own_property(LEPUSContext *ctx,
                                         LEPUSPropertyDescriptor *pdesc,
                                         LEPUSValueConst obj, JSAtom prop) {
  JSProxyData *s;
  LEPUSValue method = LEPUS_UNDEFINED, ret1, prop_val;
  HandleScope func_scope(ctx, &method, HANDLE_TYPE_LEPUS_VALUE);
  int res, target_res, ret;
  LEPUSObject *p;
  LEPUSValueConst args[2];
  func_scope.PushLEPUSValueArrayHandle(args, 2);
  LEPUSPropertyDescriptor desc, target_desc;
  func_scope.PushLEPUSPropertyDescriptor(&desc);

  s = get_proxy_method(ctx, &method, obj, JS_ATOM_getOwnPropertyDescriptor);
  if (!s) return -1;
  p = LEPUS_VALUE_GET_OBJ(s->target);
  if (LEPUS_IsUndefined(method)) {
    return JS_GetOwnPropertyInternal(ctx, pdesc, p, prop);
  }
  prop_val = JS_AtomToValue_GC(ctx, prop);
  if (LEPUS_IsException(prop_val)) {
    return -1;
  }
  args[0] = s->target;
  args[1] = prop_val;
  ret1 = JS_CallFree_GC(ctx, method, s->handler, 2, args);
  if (LEPUS_IsException(ret1)) return -1;
  func_scope.PushHandle(&ret1, HANDLE_TYPE_LEPUS_VALUE);
  if (!LEPUS_IsObject(ret1) && !LEPUS_IsUndefined(ret1)) {
    goto fail;
  }
  target_res = JS_GetOwnPropertyInternal(ctx, &target_desc, p, prop);
  if (target_res < 0) {
    return -1;
  }
  if (LEPUS_IsUndefined(ret1)) {
    if (target_res) {
      if (!(target_desc.flags & LEPUS_PROP_CONFIGURABLE) || !p->extensible)
        goto fail;
    }
    ret = FALSE;
  } else {
    int flags1;

    res = js_obj_to_desc(ctx, &desc, ret1);
    if (res < 0) return -1;
    if (target_res) {
      /* convert desc.flags to defineProperty flags */
      flags1 =
          desc.flags | LEPUS_PROP_HAS_CONFIGURABLE | LEPUS_PROP_HAS_ENUMERABLE;
      if (desc.flags & LEPUS_PROP_GETSET)
        flags1 |= LEPUS_PROP_HAS_GET | LEPUS_PROP_HAS_SET;
      else
        flags1 |= LEPUS_PROP_HAS_VALUE | LEPUS_PROP_HAS_WRITABLE;
      /* XXX: not complete check: need to compare value &
         getter/setter as in defineproperty */
      if (!check_define_prop_flags(target_desc.flags, flags1)) goto fail1;
    } else {
      if (!p->extensible) goto fail1;
    }
    res = (!(desc.flags & LEPUS_PROP_CONFIGURABLE) &&
           (!target_res || (target_desc.flags & LEPUS_PROP_CONFIGURABLE)));
    if (res) {
    fail1:
    fail:
      LEPUS_ThrowTypeError(ctx, "proxy: inconsistent getOwnPropertyDescriptor");
      return -1;
    }
    ret = TRUE;
    if (pdesc) {
      *pdesc = desc;
    }
  }
  return ret;
}

static int js_proxy_define_own_property(LEPUSContext *ctx, LEPUSValueConst obj,
                                        JSAtom prop, LEPUSValueConst val,
                                        LEPUSValueConst getter,
                                        LEPUSValueConst setter, int flags) {
  JSProxyData *s;
  LEPUSValue method = LEPUS_UNDEFINED, ret1, prop_val, desc_val;
  HandleScope func_scope(ctx, &method, HANDLE_TYPE_LEPUS_VALUE);
  int res, ret;
  LEPUSObject *p;
  LEPUSValueConst args[3];
  func_scope.PushLEPUSValueArrayHandle(args, 3);
  LEPUSPropertyDescriptor desc;
  func_scope.PushLEPUSPropertyDescriptor(&desc);
  BOOL setting_not_configurable;

  s = get_proxy_method(ctx, &method, obj, JS_ATOM_defineProperty);
  if (!s) return -1;
  if (LEPUS_IsUndefined(method)) {
    return JS_DefineProperty_GC(ctx, s->target, prop, val, getter, setter,
                                flags);
  }
  prop_val = JS_AtomToValue_GC(ctx, prop);
  if (LEPUS_IsException(prop_val)) {
    return -1;
  }
  func_scope.PushHandle(&prop_val, HANDLE_TYPE_LEPUS_VALUE);
  desc_val = js_create_desc(ctx, val, getter, setter, flags);
  if (LEPUS_IsException(desc_val)) {
    return -1;
  }
  args[0] = s->target;
  args[1] = prop_val;
  args[2] = desc_val;
  ret1 = JS_CallFree_GC(ctx, method, s->handler, 3, args);
  if (LEPUS_IsException(ret1)) return -1;
  func_scope.PushHandle(&ret1, HANDLE_TYPE_LEPUS_VALUE);
  ret = JS_ToBoolFree_GC(ctx, ret1);
  if (!ret) {
    if (flags & LEPUS_PROP_THROW) {
      LEPUS_ThrowTypeError(ctx, "proxy: defineProperty exception");
      return -1;
    } else {
      return 0;
    }
  }
  p = LEPUS_VALUE_GET_OBJ(s->target);
  res = JS_GetOwnPropertyInternal(ctx, &desc, p, prop);
  if (res < 0) return -1;
  setting_not_configurable =
      ((flags & (LEPUS_PROP_HAS_CONFIGURABLE | LEPUS_PROP_CONFIGURABLE)) ==
       LEPUS_PROP_HAS_CONFIGURABLE);
  if (!res) {
    if (!p->extensible || setting_not_configurable) goto fail;
  } else {
    if (!check_define_prop_flags(desc.flags, flags) ||
        ((desc.flags & LEPUS_PROP_CONFIGURABLE) && setting_not_configurable)) {
      goto fail1;
    }
    if (flags & (LEPUS_PROP_HAS_GET | LEPUS_PROP_HAS_SET)) {
      if ((desc.flags & (LEPUS_PROP_GETSET | LEPUS_PROP_CONFIGURABLE)) ==
          LEPUS_PROP_GETSET) {
        if ((flags & LEPUS_PROP_HAS_GET) &&
            !js_same_value(ctx, getter, desc.getter)) {
          goto fail1;
        }
        if ((flags & LEPUS_PROP_HAS_SET) &&
            !js_same_value(ctx, setter, desc.setter)) {
          goto fail1;
        }
      }
    } else if (flags & LEPUS_PROP_HAS_VALUE) {
      if ((desc.flags & (LEPUS_PROP_CONFIGURABLE | LEPUS_PROP_WRITABLE)) == 0 &&
          !js_same_value(ctx, val, desc.value)) {
      fail1:
      fail:
        LEPUS_ThrowTypeError(ctx, "proxy: inconsistent defineProperty");
        return -1;
      }
    }
  }
  return 1;
}

QJS_STATIC int js_proxy_delete_property(LEPUSContext *ctx, LEPUSValueConst obj,
                                        JSAtom atom) {
  JSProxyData *s;
  LEPUSValue method = LEPUS_UNDEFINED, ret, atom_val;
  HandleScope func_scope(ctx, &method, HANDLE_TYPE_LEPUS_VALUE);
  int res, res2;
  LEPUSValueConst args[2];
  func_scope.PushLEPUSValueArrayHandle(args, 2);

  s = get_proxy_method(ctx, &method, obj, JS_ATOM_deleteProperty);
  if (!s) return -1;
  if (LEPUS_IsUndefined(method)) {
    return JS_DeleteProperty_GC(ctx, s->target, atom, 0);
  }
  atom_val = JS_AtomToValue_GC(ctx, atom);
  if (LEPUS_IsException(atom_val)) {
    return -1;
  }
  args[0] = s->target;
  args[1] = atom_val;
  ret = JS_CallFree_GC(ctx, method, s->handler, 2, args);
  if (LEPUS_IsException(ret)) return -1;
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
  res = JS_ToBoolFree_GC(ctx, ret);
  if (res) {
    LEPUSPropertyDescriptor desc;
    res2 = JS_GetOwnPropertyInternal(ctx, &desc, LEPUS_VALUE_GET_OBJ(s->target),
                                     atom);
    if (res2 < 0) return -1;
    if (res2) {
      res2 = !(desc.flags & LEPUS_PROP_CONFIGURABLE);
      if (res2) {
        LEPUS_ThrowTypeError(ctx, "proxy: inconsistent deleteProperty");
        return -1;
      }
    }
  }
  return res;
}

/* return the index of the property or -1 if not found */
static int find_prop_key(const LEPUSPropertyEnum *tab, int n, JSAtom atom) {
  int i;
  for (i = 0; i < n; i++) {
    if (tab[i].atom == atom) return i;
  }
  return -1;
}

static int js_proxy_get_own_property_names(LEPUSContext *ctx,
                                           LEPUSPropertyEnum **ptab,
                                           uint32_t *plen,
                                           LEPUSValueConst obj) {
  JSProxyData *s;
  LEPUSValue method = LEPUS_UNDEFINED, prop_array, val = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &method, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  uint32_t len, i, len2;
  LEPUSPropertyEnum *tab = nullptr, *tab2 = nullptr;
  func_scope.PushHandle(&tab, HANDLE_TYPE_HEAP_OBJ);
  func_scope.PushHandle(&tab2, HANDLE_TYPE_HEAP_OBJ);
  JSAtom atom;
  LEPUSPropertyDescriptor desc;
  int res, is_extensible, idx;

  s = get_proxy_method(ctx, &method, obj, JS_ATOM_ownKeys);
  if (!s) return -1;
  if (LEPUS_IsUndefined(method)) {
    return JS_GetOwnPropertyNamesInternal(
        ctx, ptab, plen, LEPUS_VALUE_GET_OBJ(s->target),
        LEPUS_GPN_STRING_MASK | LEPUS_GPN_SYMBOL_MASK);
  }
  prop_array = JS_CallFree_GC(ctx, method, s->handler, 1,
                              reinterpret_cast<LEPUSValueConst *>(&s->target));
  if (LEPUS_IsException(prop_array)) return -1;
  func_scope.PushHandle(&prop_array, HANDLE_TYPE_LEPUS_VALUE);
  tab = NULL;
  len = 0;
  tab2 = NULL;
  len2 = 0;
  if (js_get_length32_gc(ctx, &len, prop_array)) goto fail;
  if (len > 0) {
    tab = static_cast<LEPUSPropertyEnum *>(
        lepus_mallocz(ctx, sizeof(tab[0]) * len, ALLOC_TAG_LEPUSPropertyEnum));
    if (!tab) goto fail;
    set_heap_obj_len(tab, len);
  }
  for (i = 0; i < len; i++) {
    val = JS_GetPropertyUint32_GC(ctx, prop_array, i);
    if (LEPUS_IsException(val)) goto fail;
    if (!LEPUS_IsString(val) && !LEPUS_IsSymbol(val)) {
      LEPUS_ThrowTypeError(ctx, "proxy: properties must be strings or symbols");
      goto fail;
    }
    atom = js_value_to_atom_gc(ctx, val);
    if (atom == JS_ATOM_NULL) goto fail;
    tab[i].atom = atom;
    tab[i].is_enumerable = FALSE; /* XXX: redundant? */
  }

  /* check duplicate properties (XXX: inefficient, could store the
   * properties an a temporary object to use the hash) */
  for (i = 1; i < len; i++) {
    if (find_prop_key(tab, i, tab[i].atom) >= 0) {
      LEPUS_ThrowTypeError(ctx, "proxy: duplicate property");
      goto fail;
    }
  }

  is_extensible = JS_IsExtensible_GC(ctx, s->target);
  if (is_extensible < 0) goto fail;

  /* check if there are non configurable properties */
  if (s->is_revoked) {
    JS_ThrowTypeErrorRevokedProxy(ctx);
    goto fail;
  }
  if (JS_GetOwnPropertyNamesInternal(
          ctx, &tab2, &len2, LEPUS_VALUE_GET_OBJ(s->target),
          LEPUS_GPN_STRING_MASK | LEPUS_GPN_SYMBOL_MASK))
    goto fail;
  for (i = 0; i < len2; i++) {
    if (s->is_revoked) {
      JS_ThrowTypeErrorRevokedProxy(ctx);
      goto fail;
    }
    res = JS_GetOwnPropertyInternal(ctx, &desc, LEPUS_VALUE_GET_OBJ(s->target),
                                    tab2[i].atom);
    if (res < 0) goto fail;
    if (res) { /* safety, property should be found */
      if (!(desc.flags & LEPUS_PROP_CONFIGURABLE) || !is_extensible) {
        idx = find_prop_key(tab, len, tab2[i].atom);
        if (idx < 0) {
          LEPUS_ThrowTypeError(
              ctx, "proxy: target property must be present in proxy ownKeys");
          goto fail;
        }
        /* mark the property as found */
        if (!is_extensible) tab[idx].is_enumerable = TRUE;
      }
    }
  }
  if (!is_extensible) {
    /* check that all property in 'tab' were checked */
    for (i = 0; i < len; i++) {
      if (!tab[i].is_enumerable) {
        LEPUS_ThrowTypeError(
            ctx,
            "proxy: property not present in target were returned "
            "by non extensible proxy");
        goto fail;
      }
    }
  }

  *ptab = tab;
  *plen = len;
  return 0;
fail:
  return -1;
}

static LEPUSValue js_proxy_call_constructor(LEPUSContext *ctx,
                                            LEPUSValueConst func_obj,
                                            LEPUSValueConst new_target,
                                            int argc, LEPUSValueConst *argv) {
  JSProxyData *s;
  LEPUSValue method = LEPUS_UNDEFINED, arg_array, ret;
  HandleScope func_scope(ctx, &method, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst args[3];
  func_scope.PushLEPUSValueArrayHandle(args, 3);

  s = get_proxy_method(ctx, &method, func_obj, JS_ATOM_construct);
  if (!s) return LEPUS_EXCEPTION;
  if (!LEPUS_IsConstructor(ctx, s->target))
    return LEPUS_ThrowTypeError(ctx, "not a constructor");
  if (LEPUS_IsUndefined(method))
    return JS_CallConstructor2_GC(ctx, s->target, new_target, argc, argv);
  arg_array = js_create_array(ctx, argc, argv);
  if (LEPUS_IsException(arg_array)) {
    ret = LEPUS_EXCEPTION;
    goto fail;
  }
  args[0] = s->target;
  args[1] = arg_array;
  args[2] = new_target;
  ret = JS_Call_GC(ctx, method, s->handler, 3, args);
  if (!LEPUS_IsException(ret) && LEPUS_VALUE_IS_NOT_OBJECT(ret)) {
    ret = JS_ThrowTypeErrorNotAnObject(ctx);
  }
fail:
  return ret;
}

static LEPUSValue js_proxy_call(LEPUSContext *ctx, LEPUSValueConst func_obj,
                                LEPUSValueConst this_obj, int argc,
                                LEPUSValueConst *argv, int flags) {
  JSProxyData *s;
  LEPUSValue method = LEPUS_UNDEFINED, arg_array, ret;
  HandleScope func_scope(ctx, &method, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst args[3];
  func_scope.PushLEPUSValueArrayHandle(args, 3);
  if (flags & LEPUS_CALL_FLAG_CONSTRUCTOR)
    return js_proxy_call_constructor(ctx, func_obj, this_obj, argc, argv);

  s = get_proxy_method(ctx, &method, func_obj, JS_ATOM_apply);
  if (!s) return LEPUS_EXCEPTION;
  if (!s->is_func) {
    return LEPUS_ThrowTypeError(ctx, "not a function");
  }
  if (LEPUS_IsUndefined(method))
    return JS_Call_GC(ctx, s->target, this_obj, argc, argv);
  arg_array = js_create_array(ctx, argc, argv);
  if (LEPUS_IsException(arg_array)) {
    ret = LEPUS_EXCEPTION;
    goto fail;
  }
  args[0] = s->target;
  args[1] = this_obj;
  args[2] = arg_array;
  ret = JS_Call_GC(ctx, method, s->handler, 3, args);
fail:
  return ret;
}

static int js_proxy_isArray(LEPUSContext *ctx, LEPUSValueConst obj) {
  JSProxyData *s =
      static_cast<JSProxyData *>(LEPUS_GetOpaque(obj, JS_CLASS_PROXY));
  if (!s) return FALSE;
  if (js_check_stack_overflow(ctx, 0)) {
    JS_ThrowStackOverflow(ctx);
    return -1;
  }
  if (s->is_revoked) {
    JS_ThrowTypeErrorRevokedProxy(ctx);
    return -1;
  }
  return JS_IsArray_GC(ctx, s->target);
}

static LEPUSValue js_proxy_constructor(LEPUSContext *ctx,
                                       LEPUSValueConst this_val, int argc,
                                       LEPUSValueConst *argv) {
  LEPUSValueConst target, handler;
  LEPUSValue obj;
  JSProxyData *s;

  target = argv[0];
  handler = argv[1];
  if (LEPUS_VALUE_IS_NOT_OBJECT(target) || LEPUS_VALUE_IS_NOT_OBJECT(handler))
    return JS_ThrowTypeErrorNotAnObject(ctx);
  s = static_cast<JSProxyData *>(LEPUS_GetOpaque(target, JS_CLASS_PROXY));
  if (s && s->is_revoked) goto revoked_proxy;
  s = static_cast<JSProxyData *>(LEPUS_GetOpaque(handler, JS_CLASS_PROXY));
  if (s && s->is_revoked) {
  revoked_proxy:
    return JS_ThrowTypeErrorRevokedProxy(ctx);
  }

  obj = JS_NewObjectProtoClass_GC(ctx, LEPUS_NULL, JS_CLASS_PROXY);
  if (LEPUS_IsException(obj)) return obj;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  s = static_cast<JSProxyData *>(
      lepus_malloc(ctx, sizeof(JSProxyData), ALLOC_TAG_JSProxyData));
  if (!s) {
    return LEPUS_EXCEPTION;
  }
  s->target = target;
  s->handler = handler;
  s->proto = LEPUS_NULL;
  s->is_func = LEPUS_IsFunction(ctx, target);
  s->is_revoked = FALSE;
  LEPUS_SetOpaque(obj, s);
  LEPUS_SetConstructorBit(ctx, obj, LEPUS_IsConstructor(ctx, target));
  return obj;
}

static LEPUSValue js_proxy_revoke(LEPUSContext *ctx, LEPUSValueConst this_val,
                                  int argc, LEPUSValueConst *argv, int magic,
                                  LEPUSValue *func_data) {
  JSProxyData *s =
      static_cast<JSProxyData *>(LEPUS_GetOpaque(func_data[0], JS_CLASS_PROXY));
  if (s) {
    /* We do not free the handler and target in case they are
       referenced as constants in the C call stack */
    s->is_revoked = TRUE;
    func_data[0] = LEPUS_NULL;
  }
  return LEPUS_UNDEFINED;
}

static LEPUSValue js_proxy_revoke_constructor(LEPUSContext *ctx,
                                              LEPUSValueConst proxy_obj) {
  return JS_NewCFunctionData_GC(ctx, js_proxy_revoke, 0, 0, 1, &proxy_obj);
}

static LEPUSValue js_proxy_revocable(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv) {
  LEPUSValue proxy_obj, revoke_obj = LEPUS_UNDEFINED, obj;

  proxy_obj = js_proxy_constructor(ctx, LEPUS_UNDEFINED, argc, argv);
  HandleScope func_scope(ctx, &proxy_obj, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_IsException(proxy_obj)) goto fail;
  revoke_obj = js_proxy_revoke_constructor(ctx, proxy_obj);
  if (LEPUS_IsException(revoke_obj)) goto fail;
  func_scope.PushHandle(&revoke_obj, HANDLE_TYPE_LEPUS_VALUE);
  obj = JS_NewObject_GC(ctx);
  if (LEPUS_IsException(obj)) goto fail;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  // XXX: exceptions?
  JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_proxy, proxy_obj,
                            LEPUS_PROP_C_W_E);
  JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_revoke, revoke_obj,
                            LEPUS_PROP_C_W_E);
  return obj;
fail:
  return LEPUS_EXCEPTION;
}

static const LEPUSCFunctionListEntry js_proxy_funcs[] = {
    LEPUS_CFUNC_DEF("revocable", 2, js_proxy_revocable),
};

static const JSClassShortDef js_proxy_class_def[] = {
    {JS_ATOM_Object, js_proxy_finalizer, js_proxy_mark}, /* JS_CLASS_PROXY */
};

void JS_AddIntrinsicProxy_GC(LEPUSContext *ctx) {
  LEPUSRuntime *rt = ctx->rt;
  LEPUSValue obj1;

  if (!LEPUS_IsRegisteredClass(rt, JS_CLASS_PROXY)) {
    init_class_range(rt, js_proxy_class_def, JS_CLASS_PROXY,
                     countof(js_proxy_class_def));
    rt->class_array[JS_CLASS_PROXY].exotic = &js_proxy_exotic_methods;
    rt->class_array[JS_CLASS_PROXY].call = js_proxy_call;
  }

  /* additonal fields: name length */
  obj1 = JS_NewCFunction3(ctx, js_proxy_constructor, "Proxy", 2,
                          LEPUS_CFUNC_constructor, 0, ctx->function_proto,
                          countof(js_proxy_funcs) + 2);
  HandleScope func_scope(ctx, &obj1, HANDLE_TYPE_LEPUS_VALUE);
  LEPUS_SetConstructorBit(ctx, obj1, TRUE);
  JS_SetPropertyFunctionList_GC(ctx, obj1, js_proxy_funcs,
                                countof(js_proxy_funcs));
  JS_DefinePropertyValueStr_GC(ctx, ctx->global_obj, "Proxy", obj1,
                               LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE);
}

/* Symbol */

static LEPUSValue js_symbol_constructor(LEPUSContext *ctx,
                                        LEPUSValueConst new_target, int argc,
                                        LEPUSValueConst *argv) {
  HandleScope func_scope(ctx);
  LEPUSValue str;
  JSString *p;

  if (!LEPUS_IsUndefined(new_target))
    return LEPUS_ThrowTypeError(ctx, "not a constructor");
  if (argc == 0 || LEPUS_IsUndefined(argv[0])) {
    p = NULL;
  } else {
    str = JS_ToString_GC(ctx, argv[0]);
    if (LEPUS_IsException(str)) return LEPUS_EXCEPTION;
    func_scope.PushHandle(&str, HANDLE_TYPE_LEPUS_VALUE);
    p = LEPUS_VALUE_GET_STRING(str);
  }
  return JS_NewSymbol(ctx, p, JS_ATOM_TYPE_SYMBOL);
}

static LEPUSValue js_thisSymbolValue(LEPUSContext *ctx,
                                     LEPUSValueConst this_val) {
  if (LEPUS_VALUE_IS_SYMBOL(this_val)) return this_val;

  if (LEPUS_VALUE_IS_OBJECT(this_val)) {
    LEPUSObject *p = LEPUS_VALUE_GET_OBJ(this_val);
    if (p->class_id == JS_CLASS_SYMBOL) {
      if (LEPUS_VALUE_IS_SYMBOL(p->u.object_data)) return p->u.object_data;
    }
  }
  return LEPUS_ThrowTypeError(ctx, "not a symbol");
}

QJS_HIDE LEPUSValue js_symbol_toString_GC(LEPUSContext *ctx,
                                          LEPUSValueConst this_val, int argc,
                                          LEPUSValueConst *argv) {
  LEPUSValue val, ret;
  val = js_thisSymbolValue(ctx, this_val);
  if (LEPUS_IsException(val)) return val;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  /* XXX: use JS_ToStringInternal() with a flags */
  ret = js_string_constructor(ctx, LEPUS_UNDEFINED, 1,
                              reinterpret_cast<LEPUSValueConst *>(&val));
  return ret;
}

static LEPUSValue js_symbol_valueOf(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
  return js_thisSymbolValue(ctx, this_val);
}

static LEPUSValue js_symbol_get_description(LEPUSContext *ctx,
                                            LEPUSValueConst this_val) {
  LEPUSValue val, ret;
  JSAtomStruct *p;

  val = js_thisSymbolValue(ctx, this_val);
  if (LEPUS_IsException(val)) return val;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  p = static_cast<JSAtomStruct *>(LEPUS_VALUE_GET_PTR(val));
  if (p->len == 0 && p->is_wide_char != 0) {
    ret = LEPUS_UNDEFINED;
  } else {
    ret = JS_AtomToString_GC(ctx, js_get_atom_index(ctx->rt, p));
  }
  return ret;
}

static const LEPUSCFunctionListEntry js_symbol_proto_funcs[] = {
    LEPUS_CFUNC_DEF("toString", 0, js_symbol_toString_GC),
    LEPUS_CFUNC_DEF("valueOf", 0, js_symbol_valueOf),
    // XXX: should have writable: false
    LEPUS_CFUNC_DEF("[Symbol.toPrimitive]", 1, js_symbol_valueOf),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "Symbol",
                          LEPUS_PROP_CONFIGURABLE),
    LEPUS_CGETSET_DEF("description", js_symbol_get_description, NULL),
};

static LEPUSValue js_symbol_for(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv) {
  LEPUSValue str;

  str = JS_ToString_GC(ctx, argv[0]);
  if (LEPUS_IsException(str)) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, &str, HANDLE_TYPE_LEPUS_VALUE);
  return JS_NewSymbol(ctx, LEPUS_VALUE_GET_STRING(str),
                      JS_ATOM_TYPE_GLOBAL_SYMBOL);
}

static LEPUSValue js_symbol_keyFor(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
  JSAtomStruct *p;

  if (!LEPUS_IsSymbol(argv[0]))
    return LEPUS_ThrowTypeError(ctx, "not a symbol");
  p = static_cast<JSAtomStruct *>(LEPUS_VALUE_GET_PTR(argv[0]));
  if (p->atom_type != JS_ATOM_TYPE_GLOBAL_SYMBOL) return LEPUS_UNDEFINED;
  return LEPUS_MKPTR(LEPUS_TAG_STRING, p);
}

static const LEPUSCFunctionListEntry js_symbol_funcs[] = {
    LEPUS_CFUNC_DEF("for", 1, js_symbol_for),
    LEPUS_CFUNC_DEF("keyFor", 1, js_symbol_keyFor),
    LEPUS_PROP_ATOM_DEF("toPrimitive", JS_ATOM_Symbol_toPrimitive, 0),
    LEPUS_PROP_ATOM_DEF("iterator", JS_ATOM_Symbol_iterator, 0),
    LEPUS_PROP_ATOM_DEF("match", JS_ATOM_Symbol_match, 0),
    LEPUS_PROP_ATOM_DEF("matchAll", JS_ATOM_Symbol_matchAll, 0),
    LEPUS_PROP_ATOM_DEF("replace", JS_ATOM_Symbol_replace, 0),
    LEPUS_PROP_ATOM_DEF("search", JS_ATOM_Symbol_search, 0),
    LEPUS_PROP_ATOM_DEF("split", JS_ATOM_Symbol_split, 0),
    LEPUS_PROP_ATOM_DEF("toStringTag", JS_ATOM_Symbol_toStringTag, 0),
    LEPUS_PROP_ATOM_DEF("isConcatSpreadable", JS_ATOM_Symbol_isConcatSpreadable,
                        0),
    LEPUS_PROP_ATOM_DEF("hasInstance", JS_ATOM_Symbol_hasInstance, 0),
    LEPUS_PROP_ATOM_DEF("species", JS_ATOM_Symbol_species, 0),
    LEPUS_PROP_ATOM_DEF("unscopables", JS_ATOM_Symbol_unscopables, 0),
    LEPUS_PROP_ATOM_DEF("asyncIterator", JS_ATOM_Symbol_asyncIterator, 0),
};

static void free_weakref_prop(LEPUSRuntime *rt, LEPUSObject *p, JSShape *sh) {
  JSShapeProperty *pr = get_shape_prop(sh);
  js_free_shape(rt, sh);  // p->shape
  p->shape = NULL;
  p->prop = NULL;
}

static void js_weakref_finalizer(LEPUSRuntime *rt, LEPUSValue val) {
  // trace_gc, remove
}

static void js_finalizationRegistry_finalizer(LEPUSRuntime *rt,
                                              LEPUSValue val) {
  // trace_gc, remove
}

static void js_finalizationRegistry_mark(LEPUSRuntime *rt, LEPUSValueConst val,
                                         LEPUS_MarkFunc *mark_func,
                                         uint64_t trace_tool) {
  // trace_gc, remove
  LEPUSObject *p = LEPUS_VALUE_GET_OBJ(val);
  LEPUSValue ccb = p->prop[0].u.value;
  if (!LEPUS_IsNull(ccb)) return;
  JS_MarkValue_GC(rt, ccb, mark_func, trace_tool);
}

static LEPUSValue js_map_constructor(LEPUSContext *ctx,
                                     LEPUSValueConst new_target, int argc,
                                     LEPUSValueConst *argv, int magic) {
  JSMapState *s;
  LEPUSValue obj, adder = LEPUS_UNDEFINED, iter = LEPUS_UNDEFINED,
                  next_method = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &adder, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&iter, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&next_method, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst arr;
  BOOL is_set, is_weak;

  is_set = magic & MAGIC_SET;
  is_weak = ((magic & MAGIC_WEAK) != 0);
  obj = js_create_from_ctor_GC(ctx, new_target, JS_CLASS_MAP + magic);
  if (LEPUS_IsException(obj)) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  s = static_cast<JSMapState *>(
      lepus_mallocz(ctx, sizeof(*s), ALLOC_TAG_JSMapState));
  if (!s) goto fail;
  func_scope.PushHandle(s, HANDLE_TYPE_DIR_HEAP_OBJ);
  init_list_head(&s->records);
  s->is_weak = is_weak;
  LEPUS_SetOpaque(obj, s);
  s->hash_bits = 1;
  s->hash_size = 1U << s->hash_bits;
  s->hash_table = static_cast<struct list_head *>(lepus_malloc(
      ctx, sizeof(s->hash_table[0]) * s->hash_size, ALLOC_TAG_WITHOUT_PTR));
  if (!s->hash_table) goto fail;
  init_list_head(&s->hash_table[0]);
  init_list_head(&s->hash_table[1]);
  s->record_count_threshold = 4;

  arr = LEPUS_UNDEFINED;
  if (argc > 0) arr = argv[0];
  if (!LEPUS_IsUndefined(arr) && !LEPUS_IsNull(arr)) {
    HandleScope block_scope(ctx->rt);
    LEPUSValue item = LEPUS_UNDEFINED, ret = LEPUS_UNDEFINED;
    block_scope.PushHandle(&item, HANDLE_TYPE_LEPUS_VALUE);
    block_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
    BOOL done;

    adder = JS_GetPropertyInternal_GC(
        ctx, obj, is_set ? JS_ATOM_add : JS_ATOM_set, obj, 0);
    if (LEPUS_IsException(adder)) goto fail;
    if (!LEPUS_IsFunction(ctx, adder)) {
      LEPUS_ThrowTypeError(ctx, "set/add is not a function");
      goto fail;
    }

    iter = JS_GetIterator(ctx, arr, FALSE);
    if (LEPUS_IsException(iter)) goto fail;
    next_method = JS_GetPropertyInternal_GC(ctx, iter, JS_ATOM_next, iter, 0);
    if (LEPUS_IsException(next_method)) goto fail;

    for (;;) {
      item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
      if (LEPUS_IsException(item)) goto fail;
      if (done) {
        break;
      }
      if (is_set) {
        ret = JS_Call_GC(ctx, adder, obj, 1,
                         reinterpret_cast<LEPUSValueConst *>(&item));
        if (LEPUS_IsException(ret)) {
          goto fail;
        }
      } else {
        HandleScope block_scope(ctx->rt);
        LEPUSValue key, value;
        LEPUSValueConst args[2];
        ctx->ptr_handles->PushLEPUSValueArrayHandle(args, 2);
        key = LEPUS_UNDEFINED;
        value = LEPUS_UNDEFINED;
        block_scope.PushHandle(&key, HANDLE_TYPE_LEPUS_VALUE);
        block_scope.PushHandle(&value, HANDLE_TYPE_LEPUS_VALUE);
        if (!LEPUS_IsObject(item)) {
          JS_ThrowTypeErrorNotAnObject(ctx);
          goto fail1;
        }
        key = JS_GetPropertyUint32_GC(ctx, item, 0);
        if (LEPUS_IsException(key)) goto fail1;
        value = JS_GetPropertyUint32_GC(ctx, item, 1);
        if (LEPUS_IsException(value)) goto fail1;
        args[0] = key;
        args[1] = value;
        ret = JS_Call_GC(ctx, adder, obj, 2, args);
        if (LEPUS_IsException(ret)) {
        fail1:
          goto fail;
        }
      }
    }
  }
  return obj;
fail:
  if (LEPUS_IsObject(iter)) {
    /* close the iterator object, preserving pending exception */
    JS_IteratorClose(ctx, iter, TRUE);
  }
  return LEPUS_EXCEPTION;
}

/* XXX: could normalize strings to speed up comparison */
static LEPUSValueConst map_normalize_key(LEPUSContext *ctx,
                                         LEPUSValueConst key) {
  /* convert -0.0 to +0.0 */
  if (LEPUS_VALUE_IS_FLOAT64(key) && LEPUS_VALUE_GET_FLOAT64(key) == 0.0) {
    key = LEPUS_NewInt32(ctx, 0);
  }
  return key;
}

static JSMapRecord *map_find_record(LEPUSContext *ctx, JSMapState *s,
                                    LEPUSValueConst key) {
  struct list_head *el, *el1;
  JSMapRecord *mr;
  uint32_t h;

  h = map_hash_key(ctx, key, s->hash_bits);
  list_for_each_safe(el, el1, &s->hash_table[h]) {
    mr = list_entry(el, JSMapRecord, hash_link);
    if (js_same_value_zero(ctx, mr->key, key)) return mr;
  }
  return NULL;
}

static void map_hash_resize(LEPUSContext *ctx, JSMapState *s) {
  uint32_t new_hash_size, i, h, new_hash_bits;
  size_t slack;
  struct list_head *new_hash_table, *el;
  JSMapRecord *mr;

  /* XXX: no reporting of memory allocation failure */
  new_hash_bits = min_int(s->hash_bits + 1, 31);
  new_hash_size = 1U << new_hash_bits;
  new_hash_table = static_cast<struct list_head *>(lepus_realloc2(
      ctx, s->hash_table, sizeof(new_hash_table[0]) * new_hash_size, &slack,
      ALLOC_TAG_WITHOUT_PTR));
  if (!new_hash_table) return;
  HandleScope func_scope(ctx, new_hash_table, HANDLE_TYPE_DIR_HEAP_OBJ);
  new_hash_size += slack / sizeof(*new_hash_table);

  for (i = 0; i < new_hash_size; i++) init_list_head(&new_hash_table[i]);

  list_for_each(el, &s->records) {
    mr = list_entry(el, JSMapRecord, link);
    if (!mr->empty) {
      h = map_hash_key(ctx, mr->key, new_hash_bits);
      list_add_tail(&mr->hash_link, &new_hash_table[h]);
    }
  }
  s->hash_table = new_hash_table;
  s->hash_size = new_hash_size;
  s->hash_bits = new_hash_bits;
  s->record_count_threshold = new_hash_size * 2;
}

static JSMapRecord *map_add_record(LEPUSContext *ctx, JSMapState *s,
                                   LEPUSValueConst key) {
  uint32_t h;
  JSMapRecord *mr;
  WeakRefRecord *wr;

  mr = static_cast<JSMapRecord *>(
      lepus_malloc(ctx, sizeof(*mr), ALLOC_TAG_WITHOUT_PTR));
  if (!mr) return NULL;
  HandleScope func_scope(ctx, mr, HANDLE_TYPE_DIR_HEAP_OBJ);
  mr->ref_count = 1;
  mr->map = s;
  mr->empty = FALSE;
  mr->value = LEPUS_UNDEFINED;
  mr->key = LEPUS_UNDEFINED;
  if (s->is_weak) {
    wr = static_cast<WeakRefRecord *>(
        lepus_malloc(ctx, sizeof(WeakRefRecord), ALLOC_TAG_WITHOUT_PTR));
    if (!wr) {
      return nullptr;
    }
    LEPUSObject *p = LEPUS_VALUE_GET_OBJ(key);
    wr->kind = WEAK_REF_KIND_WEAK_MAP;
    wr->u.map_record = mr;
    insert_weakref_record(LEPUS_VALUE_GET_OBJ(key), wr);
  }
  mr->key = (LEPUSValue)key;
  h = map_hash_key(ctx, key, s->hash_bits);
  list_add_tail(&mr->hash_link, &s->hash_table[h]);
  list_add_tail(&mr->link, &s->records);
  s->record_count++;
  if (s->record_count >= s->record_count_threshold) {
    map_hash_resize(ctx, s);
  }
  return mr;
}

/* Remove the weak reference from the object weak
   reference list. we don't use a doubly linked list to
   save space, assuming a given object has few weak
       references to it */
static void delete_weak_ref(LEPUSRuntime *rt, LEPUSObject *p, void *ptr) {
  struct WeakRefRecord **pwr, *wr;
  pwr = &p->first_weak_ref;
  for (;;) {
    wr = *pwr;
    assert(wr != nullptr);
    if (wr->u.ptr == ptr) {
      // find record
      break;
    }
    pwr = &wr->next_weak_ref;
  }
  assert(wr != nullptr);
  *pwr = wr->next_weak_ref;
  return;
}

static void map_delete_record(LEPUSRuntime *rt, JSMapState *s,
                              JSMapRecord *mr) {
  if (mr->empty) return;
  list_del(&mr->hash_link);
  if (s->is_weak) {
    delete_weak_ref(rt, LEPUS_VALUE_GET_OBJ(mr->key), mr);
  }
  if (--mr->ref_count == 0) {
    list_del(&mr->link);
  } else {
    /* keep a zombie record for iterators */
    mr->empty = TRUE;
    mr->key = LEPUS_UNDEFINED;
    mr->value = LEPUS_UNDEFINED;
  }
  s->record_count--;
}

static void map_decref_record(LEPUSRuntime *rt, JSMapRecord *mr) {
  if (--mr->ref_count == 0) {
    /* the record can be safely removed */
    assert(mr->empty);
    list_del(&mr->link);
  }
}

static LEPUSValue js_map_set(LEPUSContext *ctx, LEPUSValueConst this_val,
                             int argc, LEPUSValueConst *argv, int magic) {
  JSMapState *s = static_cast<JSMapState *>(
      LEPUS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic));
  JSMapRecord *mr;
  LEPUSValueConst key, value;

  if (!s) return LEPUS_EXCEPTION;
  key = map_normalize_key(ctx, argv[0]);
  HandleScope func_scope(ctx, &key, HANDLE_TYPE_LEPUS_VALUE);
  if (s->is_weak && LEPUS_VALUE_IS_NOT_OBJECT(key)) {
#ifdef ENABLE_LEPUSNG
    key = JSRef2Value(ctx, key);
    if (LEPUS_VALUE_IS_NOT_OBJECT(key))
#endif
      return JS_ThrowTypeErrorNotAnObject(ctx);
  }
  if (magic & MAGIC_SET)
    value = LEPUS_UNDEFINED;
  else
    value = argv[1];
  func_scope.PushHandle(&value, HANDLE_TYPE_LEPUS_VALUE);
  mr = map_find_record(ctx, s, key);
  if (!mr) {
    mr = map_add_record(ctx, s, key);
    if (!mr) return LEPUS_EXCEPTION;
  }
  mr->value = value;
  return this_val;
}

static LEPUSValue js_map_get(LEPUSContext *ctx, LEPUSValueConst this_val,
                             int argc, LEPUSValueConst *argv, int magic) {
  JSMapState *s = static_cast<JSMapState *>(
      LEPUS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic));
  JSMapRecord *mr;
  LEPUSValueConst key;

  if (!s) return LEPUS_EXCEPTION;
  key = map_normalize_key(ctx, argv[0]);
  HandleScope func_scope(ctx, &key, HANDLE_TYPE_LEPUS_VALUE);

#ifdef ENABLE_LEPUSNG
  if (s->is_weak && LEPUS_VALUE_IS_LEPUS_REF(key)) {
    key = JSRef2Value(ctx, key);
  }
#endif

  mr = map_find_record(ctx, s, key);
  if (!mr)
    return LEPUS_UNDEFINED;
  else
    return mr->value;
}

static LEPUSValue js_map_has(LEPUSContext *ctx, LEPUSValueConst this_val,
                             int argc, LEPUSValueConst *argv, int magic) {
  JSMapState *s = static_cast<JSMapState *>(
      LEPUS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic));
  JSMapRecord *mr;
  LEPUSValueConst key;

  if (!s) return LEPUS_EXCEPTION;
  key = map_normalize_key(ctx, argv[0]);
  HandleScope func_scope(ctx, &key, HANDLE_TYPE_LEPUS_VALUE);

#ifdef ENABLE_LEPUSNG
  if (s->is_weak && LEPUS_VALUE_IS_LEPUS_REF(key)) {
    key = JSRef2Value(ctx, key);
  }
#endif

  mr = map_find_record(ctx, s, key);
  return LEPUS_NewBool(ctx, (mr != NULL));
}

static LEPUSValue js_map_delete(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv, int magic) {
  JSMapState *s = static_cast<JSMapState *>(
      LEPUS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic));
  JSMapRecord *mr;
  LEPUSValueConst key;

  if (!s) return LEPUS_EXCEPTION;
  key = map_normalize_key(ctx, argv[0]);
  HandleScope func_scope(ctx, &key, HANDLE_TYPE_LEPUS_VALUE);

#ifdef ENABLE_LEPUSNG
  if (s->is_weak && LEPUS_VALUE_IS_LEPUS_REF(key)) {
    key = JSRef2Value(ctx, key);
  }
#endif

  mr = map_find_record(ctx, s, key);
  if (!mr) return LEPUS_FALSE;
  map_delete_record(ctx->rt, s, mr);
  return LEPUS_TRUE;
}

static LEPUSValue js_map_clear(LEPUSContext *ctx, LEPUSValueConst this_val,
                               int argc, LEPUSValueConst *argv, int magic) {
  JSMapState *s = static_cast<JSMapState *>(
      LEPUS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic));
  struct list_head *el, *el1;
  JSMapRecord *mr;

  if (!s) return LEPUS_EXCEPTION;
  list_for_each_safe(el, el1, &s->records) {
    mr = list_entry(el, JSMapRecord, link);
    map_delete_record(ctx->rt, s, mr);
  }
  return LEPUS_UNDEFINED;
}

QJS_HIDE
LEPUSValue js_map_get_size_GC(LEPUSContext *ctx, LEPUSValueConst this_val,
                              int magic) {
  JSMapState *s = static_cast<JSMapState *>(
      LEPUS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic));
  if (!s) return LEPUS_EXCEPTION;
  return JS_NewUint32(ctx, s->record_count);
}

static LEPUSValue js_map_forEach(LEPUSContext *ctx, LEPUSValueConst this_val,
                                 int argc, LEPUSValueConst *argv, int magic) {
  JSMapState *s = static_cast<JSMapState *>(
      LEPUS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic));
  LEPUSValueConst func = LEPUS_UNDEFINED;
  LEPUSValueConst this_arg = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &func, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&this_arg, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue ret = LEPUS_UNDEFINED, args[3];
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushLEPUSValueArrayHandle(args, 3);
  struct list_head *el;
  JSMapRecord *mr;

  if (!s) return LEPUS_EXCEPTION;
  func = argv[0];
  if (argc > 1)
    this_arg = argv[1];
  else
    this_arg = LEPUS_UNDEFINED;
  if (check_function(ctx, func)) return LEPUS_EXCEPTION;
  /* Note: the list can be modified while traversing it, but the
     current element is locked */
  el = s->records.next;
  while (el != &s->records) {
    mr = list_entry(el, JSMapRecord, link);
    if (!mr->empty) {
      mr->ref_count++;
      /* must duplicate in case the record is deleted */
      args[1] = mr->key;
      if (magic)
        args[0] = args[1];
      else
        args[0] = mr->value;
      args[2] = (LEPUSValue)this_val;
      ret = JS_Call_GC(ctx, func, this_arg, 3,
                       reinterpret_cast<LEPUSValueConst *>(args));
      el = el->next;
      map_decref_record(ctx->rt, mr);
      if (LEPUS_IsException(ret)) return ret;
    } else {
      el = el->next;
    }
  }
  return LEPUS_UNDEFINED;
}

/* Map Iterator */

typedef struct JSMapIteratorData {
  LEPUSValue obj;
  JSIteratorKindEnum kind;
  JSMapRecord *cur_record;
} JSMapIteratorData;

static LEPUSValue js_create_map_iterator(LEPUSContext *ctx,
                                         LEPUSValueConst this_val, int argc,
                                         LEPUSValueConst *argv, int magic) {
  JSIteratorKindEnum kind;
  JSMapState *s;
  JSMapIteratorData *it;
  LEPUSValue enum_obj;
  HandleScope func_scope(ctx->rt);

  kind = static_cast<JSIteratorKindEnum>(magic >> 2);
  magic &= 3;
  s = static_cast<JSMapState *>(
      LEPUS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic));
  if (!s) return LEPUS_EXCEPTION;
  enum_obj = JS_NewObjectClass_GC(ctx, JS_CLASS_MAP_ITERATOR + magic);
  if (LEPUS_IsException(enum_obj)) goto fail;
  func_scope.PushHandle(&enum_obj, HANDLE_TYPE_LEPUS_VALUE);
  it = static_cast<JSMapIteratorData *>(
      lepus_malloc(ctx, sizeof(*it), ALLOC_TAG_JSMapIteratorData));
  if (!it) {
    goto fail;
  }
  it->obj = this_val;
  it->kind = kind;
  it->cur_record = NULL;
  LEPUS_SetOpaque(enum_obj, it);
  return enum_obj;
fail:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_map_iterator_next(LEPUSContext *ctx,
                                       LEPUSValueConst this_val, int argc,
                                       LEPUSValueConst *argv, BOOL *pdone,
                                       int magic) {
  JSMapIteratorData *it;
  JSMapState *s;
  JSMapRecord *mr;
  struct list_head *el;

  it = static_cast<JSMapIteratorData *>(
      LEPUS_GetOpaque2(ctx, this_val, JS_CLASS_MAP_ITERATOR + magic));
  if (!it) {
    *pdone = FALSE;
    return LEPUS_EXCEPTION;
  }
  if (LEPUS_IsUndefined(it->obj)) goto done;
  s = static_cast<JSMapState *>(LEPUS_GetOpaque(it->obj, JS_CLASS_MAP + magic));
  assert(s != NULL);
  if (!it->cur_record) {
    el = s->records.next;
  } else {
    mr = it->cur_record;
    el = mr->link.next;
    map_decref_record(ctx->rt, mr); /* the record can be freed here */
  }
  for (;;) {
    if (el == &s->records) {
      /* no more record  */
      it->cur_record = NULL;
      it->obj = LEPUS_UNDEFINED;
    done:
      /* end of enumeration */
      *pdone = TRUE;
      return LEPUS_UNDEFINED;
    }
    mr = list_entry(el, JSMapRecord, link);
    if (!mr->empty) break;
    /* get the next record */
    el = mr->link.next;
  }

  /* lock the record so that it won't be freed */
  mr->ref_count++;
  it->cur_record = mr;
  *pdone = FALSE;

  if (it->kind == JS_ITERATOR_KIND_KEY) {
    return mr->key;
  } else {
    HandleScope block_scope(ctx->rt);
    LEPUSValueConst args[2];
    ctx->ptr_handles->PushLEPUSValueArrayHandle(args, 2);
    args[0] = mr->key;
    if (magic)
      args[1] = mr->key;
    else
      args[1] = mr->value;
    if (it->kind == JS_ITERATOR_KIND_VALUE) {
      return args[1];
    } else {
      return js_create_array(ctx, 2, args);
    }
  }
}

static const LEPUSCFunctionListEntry js_map_funcs[] = {
    LEPUS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL),
};

static const LEPUSCFunctionListEntry js_map_proto_funcs[] = {
    LEPUS_CFUNC_MAGIC_DEF("set", 2, js_map_set, 0),
    LEPUS_CFUNC_MAGIC_DEF("get", 1, js_map_get, 0),
    LEPUS_CFUNC_MAGIC_DEF("has", 1, js_map_has, 0),
    LEPUS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, 0),
    LEPUS_CFUNC_MAGIC_DEF("clear", 0, js_map_clear, 0),
    LEPUS_CGETSET_MAGIC_DEF("size", js_map_get_size_GC, NULL, 0),
    LEPUS_CFUNC_MAGIC_DEF("forEach", 1, js_map_forEach, 0),
    LEPUS_CFUNC_MAGIC_DEF("values", 0, js_create_map_iterator,
                          (JS_ITERATOR_KIND_VALUE << 2) | 0),
    LEPUS_CFUNC_MAGIC_DEF("keys", 0, js_create_map_iterator,
                          (JS_ITERATOR_KIND_KEY << 2) | 0),
    LEPUS_CFUNC_MAGIC_DEF("entries", 0, js_create_map_iterator,
                          (JS_ITERATOR_KIND_KEY_AND_VALUE << 2) | 0),
    LEPUS_ALIAS_DEF("[Symbol.iterator]", "entries"),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "Map",
                          LEPUS_PROP_CONFIGURABLE),
};

static const LEPUSCFunctionListEntry js_map_iterator_proto_funcs[] = {
    LEPUS_ITERATOR_NEXT_DEF("next", 0, js_map_iterator_next, 0),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "Map Iterator",
                          LEPUS_PROP_CONFIGURABLE),
};

static const LEPUSCFunctionListEntry js_set_proto_funcs[] = {
    LEPUS_CFUNC_MAGIC_DEF("add", 1, js_map_set, MAGIC_SET),
    LEPUS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_SET),
    LEPUS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_SET),
    LEPUS_CFUNC_MAGIC_DEF("clear", 0, js_map_clear, MAGIC_SET),
    LEPUS_CGETSET_MAGIC_DEF("size", js_map_get_size_GC, NULL, MAGIC_SET),
    LEPUS_CFUNC_MAGIC_DEF("forEach", 1, js_map_forEach, MAGIC_SET),
    LEPUS_CFUNC_MAGIC_DEF("values", 0, js_create_map_iterator,
                          (JS_ITERATOR_KIND_KEY << 2) | MAGIC_SET),
    LEPUS_ALIAS_DEF("keys", "values"),
    LEPUS_ALIAS_DEF("[Symbol.iterator]", "values"),
    LEPUS_CFUNC_MAGIC_DEF("entries", 0, js_create_map_iterator,
                          (JS_ITERATOR_KIND_KEY_AND_VALUE << 2) | MAGIC_SET),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "Set",
                          LEPUS_PROP_CONFIGURABLE),
};

static const LEPUSCFunctionListEntry js_set_iterator_proto_funcs[] = {
    LEPUS_ITERATOR_NEXT_DEF("next", 0, js_map_iterator_next, MAGIC_SET),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "Set Iterator",
                          LEPUS_PROP_CONFIGURABLE),
};

static const LEPUSCFunctionListEntry js_weak_map_proto_funcs[] = {
    LEPUS_CFUNC_MAGIC_DEF("set", 2, js_map_set, MAGIC_WEAK),
    LEPUS_CFUNC_MAGIC_DEF("get", 1, js_map_get, MAGIC_WEAK),
    LEPUS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_WEAK),
    LEPUS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_WEAK),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakMap",
                          LEPUS_PROP_CONFIGURABLE),
};

static const LEPUSCFunctionListEntry js_weak_set_proto_funcs[] = {
    LEPUS_CFUNC_MAGIC_DEF("add", 1, js_map_set, MAGIC_SET | MAGIC_WEAK),
    LEPUS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_SET | MAGIC_WEAK),
    LEPUS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_SET | MAGIC_WEAK),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakSet",
                          LEPUS_PROP_CONFIGURABLE),
};

static const LEPUSCFunctionListEntry *const js_map_proto_funcs_ptr[6] = {
    js_map_proto_funcs,          js_set_proto_funcs,
    js_weak_map_proto_funcs,     js_weak_set_proto_funcs,
    js_map_iterator_proto_funcs, js_set_iterator_proto_funcs,
};

static const uint8_t js_map_proto_funcs_count[6] = {
    countof(js_map_proto_funcs),          countof(js_set_proto_funcs),
    countof(js_weak_map_proto_funcs),     countof(js_weak_set_proto_funcs),
    countof(js_map_iterator_proto_funcs), countof(js_set_iterator_proto_funcs),
};

void JS_AddIntrinsicMapSet_GC(LEPUSContext *ctx) {
  int i;
  LEPUSValue obj1 = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &obj1, HANDLE_TYPE_LEPUS_VALUE);
  char buf[ATOM_GET_STR_BUF_SIZE];

  LEPUSCFunctionType ft = {.generic_magic = js_map_constructor};
  for (i = 0; i < 4; i++) {
    const char *name = JS_AtomGetStr(ctx, buf, sizeof(buf), JS_ATOM_Map + i);
    JS_NewCConstructor(ctx, JS_CLASS_MAP + i, name, ft.generic, 0,
                       LEPUS_CFUNC_constructor_magic, i, LEPUS_UNDEFINED,
                       js_map_funcs, i < 2 ? countof(js_map_funcs) : 0,
                       js_map_proto_funcs_ptr[i], js_map_proto_funcs_count[i],
                       0);
  }

  for (i = 0; i < 2; i++) {
    ctx->class_proto[JS_CLASS_MAP_ITERATOR + i] = JS_NewObjectProtoList(
        ctx, ctx->iterator_proto, js_map_proto_funcs_ptr[i + 4],
        js_map_proto_funcs_count[i + 4]);
  }
}

/* Generator */
static const LEPUSCFunctionListEntry js_generator_function_proto_funcs[] = {
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "GeneratorFunction",
                          LEPUS_PROP_CONFIGURABLE),
};

static const LEPUSCFunctionListEntry js_generator_proto_funcs[] = {
    LEPUS_ITERATOR_NEXT_DEF("next", 1, js_generator_next, GEN_MAGIC_NEXT),
    LEPUS_ITERATOR_NEXT_DEF("return", 1, js_generator_next, GEN_MAGIC_RETURN),
    LEPUS_ITERATOR_NEXT_DEF("throw", 1, js_generator_next, GEN_MAGIC_THROW),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "Generator",
                          LEPUS_PROP_CONFIGURABLE),
};

/* WeakRef */
static LEPUSValue js_weakref_deref_gc(LEPUSContext *ctx,
                                      LEPUSValueConst this_val, int argc,
                                      LEPUSValueConst *argv) {
  WeakRefData *data =
      static_cast<WeakRefData *>(LEPUS_GetOpaque(this_val, JS_CLASS_WeakRef));
  if (!data) {
    return LEPUS_ThrowTypeError(
        ctx, "Method WeakRef.prototype.deref called on incompatible receiver");
  }
  return data->target;
}

static const LEPUSCFunctionListEntry js_weakref_proto_funcs[] = {
    LEPUS_CFUNC_DEF("deref", 0, js_weakref_deref_gc),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakRef",
                          LEPUS_PROP_CONFIGURABLE),
};

static LEPUSValue js_weakref_constructor_gc(LEPUSContext *ctx,
                                            LEPUSValueConst new_target,
                                            int argc, LEPUSValueConst *argv,
                                            int magic) {
  if (argc < 1 || LEPUS_VALUE_IS_NOT_OBJECT(argv[0])) {
    return LEPUS_ThrowTypeError(ctx, "WeakRef: target must be an object");
  }
  if (LEPUS_IsUndefined(new_target)) {
    return LEPUS_ThrowTypeError(ctx, "constructor requires 'new'");
  }
  LEPUSValue val;
  LEPUSValue target;
  WeakRefRecord *wr = nullptr;
  WeakRefData *wrd = nullptr;

  val = js_create_from_ctor_GC(ctx, new_target, JS_CLASS_WeakRef);
  if (LEPUS_IsException(val)) return val;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  wrd = static_cast<WeakRefData *>(
      lepus_malloc(ctx, sizeof(WeakRefData), ALLOC_TAG_WeakRefData));
  if (!wrd) goto fail1;
  func_scope.PushHandle(wrd, HANDLE_TYPE_DIR_HEAP_OBJ);
  target = argv[0];
  wrd->target = target;

  wr = static_cast<WeakRefRecord *>(
      lepus_malloc(ctx, sizeof(WeakRefRecord), ALLOC_TAG_WITHOUT_PTR));
  if (!wr) goto fail1;

  wr->kind = WEAK_REF_KIND_WEAK_REF;
  wr->u.weak_ref = wrd;
  insert_weakref_record(LEPUS_VALUE_GET_OBJ(target), wr);
  LEPUS_SetOpaque(val, wrd);
  return val;

fail1:
  return LEPUS_EXCEPTION;
}

static void JS_AddIntrinsicWeakRef(LEPUSContext *ctx) {
  const char *name = "WeakRef";
  JSAtom JS_ATOM_WeakRef = LEPUS_NewAtom(ctx, name);
  HandleScope func_scope(ctx);
  func_scope.PushLEPUSAtom(JS_ATOM_WeakRef);
  JSClassShortDef const js_weak_ref_def[] = {
      {JS_ATOM_WeakRef, js_weakref_finalizer, NULL},
  };

  LEPUSRuntime *rt = ctx->rt;
  if (!LEPUS_IsRegisteredClass(rt, JS_CLASS_WeakRef)) {
    init_class_range(rt, js_weak_ref_def, JS_CLASS_WeakRef,
                     countof(js_weak_ref_def));
  }
  char buf[ATOM_GET_STR_BUF_SIZE];
  // weakref
  LEPUSCFunctionType ft;
  ft.generic_magic = js_weakref_constructor_gc;
  JS_NewCConstructor(ctx, JS_CLASS_WeakRef, name, ft.generic, 1,
                     LEPUS_CFUNC_constructor_or_func, 0, LEPUS_UNDEFINED,
                     nullptr, 0, js_weakref_proto_funcs,
                     countof(js_weakref_proto_funcs), 0);
}

LEPUSValue js_finalizationRegistry_register_gc(LEPUSContext *ctx,
                                               LEPUSValueConst this_val,
                                               int argc,
                                               LEPUSValueConst *argv) {
  LEPUSValue target, held_value, token;
  LEPUSObject *target_p;
  FinalizationRegistryData *frd;
  FinalizationRegistryEntry *fin_node;
  WeakRefRecord *wr;
  frd = static_cast<FinalizationRegistryData *>(
      LEPUS_GetOpaque(this_val, JS_CLASS_FinalizationRegistry));
  if (!frd) {
    return LEPUS_ThrowTypeError(
        ctx,
        "Method FinalizationRegistry.prototype.register called on "
        "incompatible "
        "receiver");
  }

  target = argv[0];
  if (LEPUS_VALUE_IS_NOT_OBJECT(target)) {
    return LEPUS_ThrowTypeError(
        ctx,
        "FinalizationRegistry.prototype.register: target must be an object");
  }
  target_p = LEPUS_VALUE_GET_OBJ(target);
  held_value = argv[1];
  if (LEPUS_VALUE_IS_OBJECT(held_value) &&
      (LEPUS_VALUE_GET_OBJ(held_value) == target_p)) {
    return LEPUS_ThrowTypeError(ctx,
                                "FinalizationRegistry.prototype.register: "
                                "target and holdings must not be same");
  }

  if (argc > 2) {
    token = argv[2];
    if (LEPUS_VALUE_IS_NOT_OBJECT(token)) {
      return LEPUS_ThrowTypeError(ctx, "unregisterToken must be an object");
    }
  } else {
    token = LEPUS_UNDEFINED;
  }
  wr = static_cast<WeakRefRecord *>(
      lepus_malloc(ctx, sizeof(WeakRefRecord), ALLOC_TAG_WITHOUT_PTR));
  if (!wr) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, wr, HANDLE_TYPE_DIR_HEAP_OBJ);
  fin_node = static_cast<FinalizationRegistryEntry *>(lepus_malloc(
      ctx, sizeof(FinalizationRegistryEntry), ALLOC_TAG_WITHOUT_PTR));
  if (!fin_node) {
    return LEPUS_EXCEPTION;
  }
  fin_node->data = frd;
  fin_node->target = target;
  fin_node->held_value = held_value;
  fin_node->token = token;
  list_add_tail(&fin_node->link, &frd->entries);
  wr->kind = WEAK_REF_KIND_FINALIZATION_REGISTRY;
  wr->u.fin_node = fin_node;
  insert_weakref_record(target_p, wr);
  return LEPUS_UNDEFINED;
}

LEPUSValue js_finalizationRegistry_unregister_gc(LEPUSContext *ctx,
                                                 LEPUSValueConst this_val,
                                                 int argc,
                                                 LEPUSValueConst *argv) {
  LEPUSValue token = argv[0];
  FinalizationRegistryData *frd;
  list_head *el, *el1;
  FinalizationRegistryEntry *fin_node;
  LEPUSObject *token_p;
  bool removed = false;

  frd = static_cast<FinalizationRegistryData *>(
      LEPUS_GetOpaque(this_val, JS_CLASS_FinalizationRegistry));
  if (!frd) {
    return LEPUS_ThrowTypeError(ctx,
                                "FinalizationRegistry.prototype.unregister: "
                                "called on incompatible receiver");
  }
  if (LEPUS_VALUE_IS_NOT_OBJECT(token)) {
    return LEPUS_ThrowTypeError(ctx, "unregisterToken must be an object");
  }
  token_p = LEPUS_VALUE_GET_OBJ(token);
  list_for_each_safe(el, el1, &frd->entries) {
    fin_node = list_entry(el, FinalizationRegistryEntry, link);
    if (LEPUS_VALUE_IS_OBJECT(fin_node->token) &&
        LEPUS_VALUE_GET_OBJ(fin_node->token) == token_p) {
      list_del(&fin_node->link);
      delete_weak_ref(ctx->rt, LEPUS_VALUE_GET_OBJ(fin_node->target), fin_node);
      removed = true;
    }
  }
  return LEPUS_NewBool(ctx, removed);
}

static LEPUSValue js_finalizationRegistry_constructor_gc(
    LEPUSContext *ctx, LEPUSValueConst new_target, int argc,
    LEPUSValueConst *argv, int magic) {
  LEPUSValue executor;
  LEPUSValue val;

  executor = argv[0];
  if (check_function(ctx, executor))
    return LEPUS_ThrowTypeError(
        ctx, "FinalizationRegistry: cleanup must be callable");

  if (LEPUS_IsUndefined(new_target))
    return LEPUS_ThrowTypeError(ctx, "constructor requires 'new'");
  val = js_create_from_ctor_GC(ctx, new_target, JS_CLASS_FinalizationRegistry);
  if (LEPUS_IsException(val)) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);

  FinalizationRegistryData *frd = static_cast<FinalizationRegistryData *>(
      lepus_malloc(ctx, sizeof(FinalizationRegistryData),
                   ALLOC_TAG_FinalizationRegistryData));
  if (!frd) {
    return LEPUS_EXCEPTION;
  }
  frd->fg_ctx = ctx->fg_ctx;
  init_list_head(&frd->entries);
  frd->cbs = executor;
  LEPUS_SetOpaque(val, frd);

  return val;
}

QJS_STATIC const LEPUSCFunctionListEntry
    js_finalizationRegistry_proto_funcs[] = {
        LEPUS_CFUNC_DEF("register", 3, js_finalizationRegistry_register_gc),
        LEPUS_CFUNC_DEF("unregister", 1, js_finalizationRegistry_unregister_gc),
        LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "FinalizationRegistry",
                              LEPUS_PROP_CONFIGURABLE),
};

void JS_AddIntrinsicFinalizationRegistry(LEPUSContext *ctx) {
  const char *name = "FinalizationRegistry";
  JSAtom JS_ATOM_FinalizationRegistry = LEPUS_NewAtom(ctx, name);
  HandleScope func_scope(ctx);
  func_scope.PushLEPUSAtom(JS_ATOM_FinalizationRegistry);
  JSClassShortDef const js_finalization_ref_def[] = {
      {JS_ATOM_FinalizationRegistry, js_finalizationRegistry_finalizer,
       js_finalizationRegistry_mark},
  };

  LEPUSRuntime *rt = ctx->rt;
  if (!LEPUS_IsRegisteredClass(rt, JS_CLASS_FinalizationRegistry)) {
    init_class_range(rt, js_finalization_ref_def, JS_CLASS_FinalizationRegistry,
                     countof(js_finalization_ref_def));
  }
  LEPUSCFunctionType ft = {.generic_magic =
                               js_finalizationRegistry_constructor_gc};
  JS_NewCConstructor(ctx, JS_CLASS_FinalizationRegistry, name, ft.generic, 1,
                     LEPUS_CFUNC_constructor_or_func, 0, LEPUS_UNDEFINED,
                     nullptr, 0, js_finalizationRegistry_proto_funcs,
                     countof(js_finalizationRegistry_proto_funcs), 0);
}

/* Promise */

int JS_MoveUnhandledRejectionToException_GC(LEPUSContext *ctx) {
  //  assert(LEPUS_IsNull(ctx->rt->current_exception));
  struct list_head *el = ctx->rt->unhandled_rejections.next;
  if (el == &ctx->rt->unhandled_rejections) return 0;
  JSUnhandledRejectionEntry *e =
      list_entry(el, JSUnhandledRejectionEntry, link);
  ctx->rt->current_exception = e->error;
  list_del(el);
  return 1;
}

static int js_create_resolving_functions(LEPUSContext *ctx, LEPUSValue *args,
                                         LEPUSValueConst promise);

static LEPUSValue promise_reaction_job(LEPUSContext *ctx, int argc,
                                       LEPUSValueConst *argv) {
  LEPUSValueConst handler, arg, func;
  LEPUSValue res = LEPUS_UNDEFINED, res2 = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &res, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&res2, HANDLE_TYPE_LEPUS_VALUE);
  BOOL is_reject;

  assert(argc == 5);
#ifdef BUILD_ASYNC_STACK
  ctx->rt->current_micro_task = argv;
#endif
  handler = argv[2];
  func_scope.PushHandle(&handler, HANDLE_TYPE_LEPUS_VALUE);
  is_reject = JS_ToBool_GC(ctx, argv[3]);
  arg = argv[4];
  func_scope.PushHandle(&arg, HANDLE_TYPE_LEPUS_VALUE);
#ifdef DUMP_PROMISE
  printf("promise_reaction_job: is_reject=%d\n", is_reject);
#endif

  if (LEPUS_IsUndefined(handler)) {
    if (is_reject) {
      res = LEPUS_Throw(ctx, arg);
    } else {
      res = arg;
    }
  } else {
    res = JS_Call_GC(ctx, handler, LEPUS_UNDEFINED, 1, &arg);
  }
  is_reject = LEPUS_IsException(res);
  if (is_reject) res = LEPUS_GetException(ctx);
  func = argv[is_reject];
  func_scope.PushHandle(&func, HANDLE_TYPE_LEPUS_VALUE);
  /* as an extension, we support undefined as value to avoid
     creating a dummy promise in the 'await' implementation of async
     functions */
  if (!LEPUS_IsUndefined(func)) {
    res2 = JS_Call_GC(ctx, func, LEPUS_UNDEFINED, 1,
                      reinterpret_cast<LEPUSValueConst *>(&res));
  } else {
    res2 = LEPUS_UNDEFINED;
  }
#ifdef BUILD_ASYNC_STACK
  ctx->rt->current_micro_task = NULL;
#endif
  return res2;
}

static void fulfill_or_reject_promise(LEPUSContext *ctx,
                                      LEPUSValueConst promise,
                                      LEPUSValueConst value, BOOL is_reject) {
  HandleScope func_scope(ctx, &promise, HANDLE_TYPE_LEPUS_VALUE);
  JSPromiseData *s =
      static_cast<JSPromiseData *>(LEPUS_GetOpaque(promise, JS_CLASS_PROMISE));
  struct list_head *el, *el1;
  JSPromiseReactionData *rd;
  LEPUSValueConst args[5];
  func_scope.PushLEPUSValueArrayHandle(args, 5);

  if (!s || s->promise_state != JS_PROMISE_PENDING)
    return; /* should never happen */
  set_value_gc(ctx, &s->promise_result, value);
  s->promise_state =
      static_cast<JSPromiseStateEnum>(JS_PROMISE_FULFILLED + is_reject);
#ifdef DUMP_PROMISE
  printf("fulfill_or_reject_promise: is_reject=%d\n", is_reject);
#endif
  BOOL rejection_handled = FALSE;
  /* Note: could call HostPromiseRejectTracker */
  list_for_each_safe(el, el1, &s->promise_reactions[is_reject]) {
    rd = list_entry(el, JSPromiseReactionData, link);
    rejection_handled = TRUE;
    args[0] = rd->resolving_funcs[0];
    args[1] = rd->resolving_funcs[1];
    args[2] = rd->handler;
    args[3] = LEPUS_NewBool(ctx, is_reject);
    args[4] = value;
    JS_EnqueueJob_GC(ctx, promise_reaction_job, 5, args);
    list_del(&rd->link);
  }

  list_for_each_safe(el, el1, &s->promise_reactions[1 - is_reject]) {
    rd = list_entry(el, JSPromiseReactionData, link);
    list_del(&rd->link);
  }

  if (is_reject && !rejection_handled) {
    /* Unhandled rejection detected */
    JSUnhandledRejectionEntry *e;
    e = static_cast<JSUnhandledRejectionEntry *>(
        lepus_malloc(ctx, sizeof(*e), ALLOC_TAG_WITHOUT_PTR));
    func_scope.PushHandle(e, HANDLE_TYPE_DIR_HEAP_OBJ);
    // only promises handled later will use this value, thus its refcount
    // always > 0 when using
    e->promise = promise;

    if (LEPUS_IsError(ctx, value)) {
      e->error = value;
    } else {
      e->error = js_error_constructor(ctx, promise, 1, &value, -1);
    }
    auto stack_val = LEPUS_GetProperty(ctx, e->error, JS_ATOM_stack);
    int32_t has_stack =
        LEPUS_IsString(stack_val) && LEPUS_GetLength(ctx, stack_val) > 0;
    if (has_stack) {
      list_add_tail(&e->link, &ctx->rt->unhandled_rejections);
    }
  }
}

static void reject_promise(LEPUSContext *ctx, LEPUSValueConst promise,
                           LEPUSValueConst value) {
  fulfill_or_reject_promise(ctx, promise, value, TRUE);
}

static LEPUSValue js_promise_resolve_thenable_job(LEPUSContext *ctx, int argc,
                                                  LEPUSValueConst *argv) {
  HandleScope func_scope(ctx);
  LEPUSValueConst promise, thenable, then;
  LEPUSValue res;
  LEPUSValue args[2];
  func_scope.PushLEPUSValueArrayHandle(args, 2);

#ifdef DUMP_PROMISE
  printf("js_promise_resolve_thenable_job\n");
#endif
  assert(argc == 3);
  promise = argv[0];
  thenable = argv[1];
  then = argv[2];
  func_scope.PushHandle(&promise, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&thenable, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&then, HANDLE_TYPE_LEPUS_VALUE);
  if (js_create_resolving_functions(ctx, args, promise) < 0)
    return LEPUS_EXCEPTION;
  res = JS_Call_GC(ctx, then, thenable, 2,
                   reinterpret_cast<LEPUSValueConst *>(args));
  func_scope.PushHandle(&res, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_IsException(res)) {
    HandleScope block_scope(ctx->rt);
    LEPUSValue error = LEPUS_GetException(ctx);
    block_scope.PushHandle(&error, HANDLE_TYPE_LEPUS_VALUE);
    res = JS_Call_GC(ctx, args[1], LEPUS_UNDEFINED, 1,
                     reinterpret_cast<LEPUSValueConst *>(&error));
  }
  return res;
}

static int js_create_resolving_functions(LEPUSContext *ctx,
                                         LEPUSValue *resolving_funcs,
                                         LEPUSValueConst promise) {
  LEPUSValue obj = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  JSPromiseFunctionData *s;
  JSPromiseFunctionDataResolved *sr;
  int i, ret;

  sr = static_cast<JSPromiseFunctionDataResolved *>(
      lepus_malloc(ctx, sizeof(*sr), ALLOC_TAG_WITHOUT_PTR));
  if (!sr) return -1;
  func_scope.PushHandle(sr, HANDLE_TYPE_DIR_HEAP_OBJ);
  sr->ref_count = 1;
  sr->already_resolved = FALSE; /* must be shared between the two functions */
  ret = 0;
  for (i = 0; i < 2; i++) {
    obj = JS_NewObjectProtoClass_GC(ctx, ctx->function_proto,
                                    JS_CLASS_PROMISE_RESOLVE_FUNCTION + i);
    if (LEPUS_IsException(obj)) goto fail;
    s = static_cast<JSPromiseFunctionData *>(
        lepus_malloc(ctx, sizeof(*s), ALLOC_TAG_JSPromiseFunctionData));
    if (!s) {
    fail:

      ret = -1;
      break;
    }
    sr->ref_count++;
    s->presolved = sr;
    s->promise = promise;
    LEPUS_SetOpaque(obj, s);
    js_function_set_properties(ctx, LEPUS_VALUE_GET_OBJ(obj),
                               JS_ATOM_empty_string, 1);
    resolving_funcs[i] = obj;
  }
  return ret;
}

static LEPUSValue js_promise_resolve_function_call(
    LEPUSContext *ctx, LEPUSValueConst func_obj, LEPUSValueConst this_val,
    int argc, LEPUSValueConst *argv, int flags) {
  HandleScope func_scope(ctx);
  LEPUSObject *p = LEPUS_VALUE_GET_OBJ(func_obj);
  JSPromiseFunctionData *s;
  LEPUSValueConst resolution;
  LEPUSValue args[3];
  func_scope.PushLEPUSValueArrayHandle(args, 3);
  LEPUSValue then;
  BOOL is_reject;

  s = p->u.promise_function_data;
  if (!s || s->presolved->already_resolved) return LEPUS_UNDEFINED;
  s->presolved->already_resolved = TRUE;
  is_reject = p->class_id - JS_CLASS_PROMISE_RESOLVE_FUNCTION;
  if (argc > 0)
    resolution = argv[0];
  else
    resolution = LEPUS_UNDEFINED;
  func_scope.PushHandle(&resolution, HANDLE_TYPE_LEPUS_VALUE);
#ifdef DUMP_PROMISE
  printf("js_promise_resolving_function_call: is_reject=%d resolution=",
         is_reject);
  JS_DumpValue(ctx, resolution);
  printf("\n");
#endif
  if (is_reject || !LEPUS_IsObject(resolution)) {
    goto done;
  } else if (js_same_value(ctx, resolution, s->promise)) {
    LEPUS_ThrowTypeError(ctx, "promise self resolution");
    goto fail_reject;
  }
  then =
      JS_GetPropertyInternal_GC(ctx, resolution, JS_ATOM_then, resolution, 0);
  func_scope.PushHandle(&then, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_IsException(then)) {
    LEPUSValue error;
  fail_reject:
    error = LEPUS_GetException(ctx);
    func_scope.PushHandle(&error, HANDLE_TYPE_LEPUS_VALUE);
    reject_promise(ctx, s->promise, error);
    func_scope.ResetHandle(&error, HANDLE_TYPE_LEPUS_VALUE);
  } else if (!LEPUS_IsFunction(ctx, then)) {
  done:
    fulfill_or_reject_promise(ctx, s->promise, resolution, is_reject);
  } else {
    args[0] = s->promise;
    args[1] = resolution;
    args[2] = then;
    JS_EnqueueJob_GC(ctx, js_promise_resolve_thenable_job, 3, args);
  }
  return LEPUS_UNDEFINED;
}

static LEPUSValue js_promise_constructor(LEPUSContext *ctx,
                                         LEPUSValueConst new_target, int argc,
                                         LEPUSValueConst *argv) {
  HandleScope func_scope(ctx);
  LEPUSValueConst executor;
  LEPUSValue obj;
  JSPromiseData *s;
  LEPUSValue args[2], ret;
  func_scope.PushLEPUSValueArrayHandle(args, 2);
  int i;

  executor = argv[0];
  func_scope.PushHandle(&executor, HANDLE_TYPE_LEPUS_VALUE);
  if (check_function(ctx, executor)) return LEPUS_EXCEPTION;
  obj = js_create_from_ctor_GC(ctx, new_target, JS_CLASS_PROMISE);
  if (LEPUS_IsException(obj)) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  s = static_cast<JSPromiseData *>(
      lepus_mallocz(ctx, sizeof(*s), ALLOC_TAG_JSPromiseData));
  if (!s) goto fail;
  s->promise_state = JS_PROMISE_PENDING;
  s->is_handled = FALSE;
  for (i = 0; i < 2; i++) init_list_head(&s->promise_reactions[i]);
  s->promise_result = LEPUS_UNDEFINED;
  LEPUS_SetOpaque(obj, s);
  if (js_create_resolving_functions(ctx, args, obj)) goto fail;
  ret = JS_Call_GC(ctx, executor, LEPUS_UNDEFINED, 2,
                   reinterpret_cast<LEPUSValueConst *>(args));
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_IsException(ret)) {
    HandleScope block_scope(ctx->rt);
    LEPUSValue ret2, error;
    error = LEPUS_GetException(ctx);
    block_scope.PushHandle(&error, HANDLE_TYPE_LEPUS_VALUE);
    ret2 = JS_Call_GC(ctx, args[1], LEPUS_UNDEFINED, 1,
                      reinterpret_cast<LEPUSValueConst *>(&error));
    block_scope.PushHandle(&ret2, HANDLE_TYPE_LEPUS_VALUE);
    if (LEPUS_IsException(ret2)) goto fail;
  }
  return obj;
fail:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_promise_executor(LEPUSContext *ctx,
                                      LEPUSValueConst this_val, int argc,
                                      LEPUSValueConst *argv, int magic,
                                      LEPUSValue *func_data) {
  int i;

  for (i = 0; i < 2; i++) {
    if (!LEPUS_IsUndefined(func_data[i]))
      return LEPUS_ThrowTypeError(ctx, "resolving function already set");
    func_data[i] = argv[i];
  }
  return LEPUS_UNDEFINED;
}

static LEPUSValue js_promise_executor_new(LEPUSContext *ctx) {
  HandleScope func_scope(ctx);
  LEPUSValueConst func_data[2];
  func_scope.PushLEPUSValueArrayHandle(func_data, 2);
  return JS_NewCFunctionData_GC(ctx, js_promise_executor, 2, 0, 2, func_data);
}

static LEPUSValue js_new_promise_capability(LEPUSContext *ctx,
                                            LEPUSValue *resolving_funcs,
                                            LEPUSValueConst ctor) {
  LEPUSValue executor, result_promise;
  JSCFunctionDataRecord *s;
  int i;

  executor = js_promise_executor_new(ctx);
  if (LEPUS_IsException(executor)) return executor;
  HandleScope func_scope(ctx, &executor, HANDLE_TYPE_LEPUS_VALUE);

  if (LEPUS_IsUndefined(ctor)) {
    result_promise = js_promise_constructor(
        ctx, ctor, 1, reinterpret_cast<LEPUSValueConst *>(&executor));
  } else {
    result_promise = JS_CallConstructor_GC(
        ctx, ctor, 1, reinterpret_cast<LEPUSValueConst *>(&executor));
  }
  if (LEPUS_IsException(result_promise)) goto fail;
  func_scope.PushHandle(&result_promise, HANDLE_TYPE_LEPUS_VALUE);
  s = static_cast<JSCFunctionDataRecord *>(
      LEPUS_GetOpaque(executor, JS_CLASS_C_FUNCTION_DATA));
  for (i = 0; i < 2; i++) {
    if (check_function(ctx, s->data[i])) goto fail;
  }
  for (i = 0; i < 2; i++) resolving_funcs[i] = s->data[i];
  return result_promise;
fail:
  return LEPUS_EXCEPTION;
}

LEPUSValue JS_NewPromiseCapability_GC(LEPUSContext *ctx,
                                      LEPUSValue *resolving_funcs) {
  return js_new_promise_capability(ctx, resolving_funcs, LEPUS_UNDEFINED);
}

static LEPUSValue js_promise_resolve(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv, int magic) {
  HandleScope func_scope(ctx);
  LEPUSValue result_promise, resolving_funcs[2], ret;
  func_scope.PushLEPUSValueArrayHandle(resolving_funcs, 2);
  BOOL is_reject = magic;

  if (!LEPUS_IsObject(this_val)) return JS_ThrowTypeErrorNotAnObject(ctx);
  if (!is_reject && LEPUS_GetOpaque(argv[0], JS_CLASS_PROMISE)) {
    HandleScope block_scope(ctx->rt);
    LEPUSValue ctor;
    BOOL is_same;
    ctor = JS_GetPropertyInternal_GC(ctx, argv[0], JS_ATOM_constructor, argv[0],
                                     0);
    if (LEPUS_IsException(ctor)) return ctor;
    block_scope.PushHandle(&ctor, HANDLE_TYPE_LEPUS_VALUE);
    is_same = js_same_value(ctx, ctor, this_val);
    if (is_same) return argv[0];
  }
  result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val);
  if (LEPUS_IsException(result_promise)) return result_promise;
  func_scope.PushHandle(&result_promise, HANDLE_TYPE_LEPUS_VALUE);
  ret = JS_Call_GC(ctx, resolving_funcs[is_reject], LEPUS_UNDEFINED, 1, argv);
  if (LEPUS_IsException(ret)) {
    return ret;
  }
  return result_promise;
}

#if 0
static LEPUSValue js_promise___newPromiseCapability(LEPUSContext *ctx,
                                                 LEPUSValueConst this_val,
                                                 int argc, LEPUSValueConst *argv) {
    LEPUSValue result_promise, resolving_funcs[2], obj;
    LEPUSValueConst ctor;
    ctor = argv[0];
    if (!LEPUS_IsObject(ctor))
        return JS_ThrowTypeErrorNotAnObject(ctx);
    result_promise = js_new_promise_capability(ctx, resolving_funcs, ctor);
    if (LEPUS_IsException(result_promise))
        return result_promise;
    obj = JS_NewObject_GC(ctx);
    if (LEPUS_IsException(obj)) {
        return LEPUS_EXCEPTION;
    }
    JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_promise, result_promise, LEPUS_PROP_C_W_E);
    JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_resolve, resolving_funcs[0], LEPUS_PROP_C_W_E);
    JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_reject, resolving_funcs[1], LEPUS_PROP_C_W_E);
    return obj;
}
#endif

static __exception int remainingElementsCount_add(
    LEPUSContext *ctx, LEPUSValueConst resolve_element_env, int addend) {
  LEPUSValue val;
  int remainingElementsCount;

  val = JS_GetPropertyUint32_GC(ctx, resolve_element_env, 0);
  if (LEPUS_IsException(val)) return -1;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  if (JS_ToInt32Free(ctx, &remainingElementsCount, val)) return -1;
  remainingElementsCount += addend;
  if (JS_SetPropertyUint32_GC(ctx, resolve_element_env, 0,
                              LEPUS_NewInt32(ctx, remainingElementsCount)) < 0)
    return -1;
  return (remainingElementsCount == 0);
}

/* used by C code. */
static LEPUSValue js_aggregate_error_constructor(LEPUSContext *ctx,
                                                 LEPUSValueConst errors) {
  LEPUSValue obj;

  obj = JS_NewObjectProtoClass_GC(
      ctx, ctx->native_error_proto[JS_AGGREGATE_ERROR], JS_CLASS_ERROR);
  if (LEPUS_IsException(obj)) return obj;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  JSAtom atom_errors = LEPUS_NewAtom(ctx, "errors");
  func_scope.PushLEPUSAtom(atom_errors);
  JS_DefinePropertyValue_GC(ctx, obj, atom_errors, errors,
                            LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE);
  return obj;
}

#define PROMISE_MAGIC_all 0
#define PROMISE_MAGIC_allSettled 1
#define PROMISE_MAGIC_any 2

static LEPUSValue js_promise_all_resolve_element(
    LEPUSContext *ctx, LEPUSValueConst this_val, int argc,
    LEPUSValueConst *argv, int magic, LEPUSValue *func_data) {
  int resolve_type = magic & 3;
  int is_reject = magic & 4;
  BOOL alreadyCalled = JS_ToBool_GC(ctx, func_data[0]);
  LEPUSValueConst values = func_data[2];
  LEPUSValueConst resolve = func_data[3];
  LEPUSValueConst resolve_element_env = func_data[4];
  HandleScope func_scope(ctx, &values, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&resolve, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&resolve_element_env, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue ret, obj = LEPUS_UNDEFINED;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  int is_zero, index;

  if (JS_ToInt32_GC(ctx, &index, func_data[1])) return LEPUS_EXCEPTION;
  if (alreadyCalled) return LEPUS_UNDEFINED;
  func_data[0] = LEPUS_NewBool(ctx, TRUE);

  if (resolve_type == PROMISE_MAGIC_allSettled) {
    HandleScope block_scope(ctx->rt);
    LEPUSValue str;

    obj = JS_NewObject_GC(ctx);
    if (LEPUS_IsException(obj)) return LEPUS_EXCEPTION;
    str = JS_NewString_GC(ctx, is_reject ? "rejected" : "fulfilled");
    if (LEPUS_IsException(str)) goto fail1;
    block_scope.PushHandle(&str, HANDLE_TYPE_LEPUS_VALUE);
    if (JS_DefinePropertyValue_GC(ctx, obj, JS_ATOM_status, str,
                                  LEPUS_PROP_C_W_E) < 0)
      goto fail1;
    if (JS_DefinePropertyValue_GC(ctx, obj,
                                  is_reject ? JS_ATOM_reason : JS_ATOM_value,
                                  argv[0], LEPUS_PROP_C_W_E) < 0) {
    fail1:
      return LEPUS_EXCEPTION;
    }
  } else {
    obj = argv[0];
  }
  if (JS_DefinePropertyValueUint32_GC(ctx, values, index, obj,
                                      LEPUS_PROP_C_W_E) < 0)
    return LEPUS_EXCEPTION;

  is_zero = remainingElementsCount_add(ctx, resolve_element_env, -1);
  if (is_zero < 0) return LEPUS_EXCEPTION;
  if (is_zero) {
    if (resolve_type == PROMISE_MAGIC_any) {
      HandleScope block_scope(ctx->rt);
      LEPUSValue error;
      error = js_aggregate_error_constructor(ctx, values);
      if (LEPUS_IsException(error)) return LEPUS_EXCEPTION;
      block_scope.PushHandle(&error, HANDLE_TYPE_LEPUS_VALUE);
      ret = JS_Call_GC(ctx, resolve, LEPUS_UNDEFINED, 1,
                       reinterpret_cast<LEPUSValueConst *>(&error));
    } else {
      ret = JS_Call_GC(ctx, resolve, LEPUS_UNDEFINED, 1,
                       reinterpret_cast<LEPUSValueConst *>(&values));
    }

    if (LEPUS_IsException(ret)) return ret;
  }
  return LEPUS_UNDEFINED;
}

/* magic = 0: Promise.all 1: Promise.allSettled */
static LEPUSValue js_promise_all(LEPUSContext *ctx, LEPUSValueConst this_val,
                                 int argc, LEPUSValueConst *argv, int magic) {
  HandleScope func_scope(ctx);
  LEPUSValue result_promise, resolving_funcs[2], item = LEPUS_UNDEFINED,
                                                 next_promise = LEPUS_UNDEFINED,
                                                 ret = LEPUS_UNDEFINED;
  func_scope.PushLEPUSValueArrayHandle(resolving_funcs, 2);
  func_scope.PushHandle(&item, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&next_promise, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue next_method = LEPUS_UNDEFINED, values = LEPUS_UNDEFINED;
  func_scope.PushHandle(&next_method, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&values, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue resolve_element_env = LEPUS_UNDEFINED,
             resolve_element = LEPUS_UNDEFINED,
             reject_element = LEPUS_UNDEFINED;
  func_scope.PushHandle(&resolve_element_env, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&resolve_element, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&reject_element, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue promise_resolve = LEPUS_UNDEFINED, iter = LEPUS_UNDEFINED;
  func_scope.PushHandle(&promise_resolve, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&iter, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst then_args[2];
  LEPUSValueConst resolve_element_data[5];
  func_scope.PushLEPUSValueArrayHandle(then_args, 2);
  func_scope.PushLEPUSValueArrayHandle(resolve_element_data, 5);
  BOOL done;
  int index, is_zero, is_promise_any = (magic == PROMISE_MAGIC_any);

  if (!LEPUS_IsObject(this_val)) return JS_ThrowTypeErrorNotAnObject(ctx);
  result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val);
  if (LEPUS_IsException(result_promise)) return result_promise;
  func_scope.PushHandle(&result_promise, HANDLE_TYPE_LEPUS_VALUE);
  promise_resolve =
      JS_GetPropertyInternal_GC(ctx, this_val, JS_ATOM_resolve, this_val, 0);
  if (LEPUS_IsException(promise_resolve) ||
      check_function(ctx, promise_resolve))
    goto fail_reject;
  iter = JS_GetIterator(ctx, argv[0], FALSE);
  if (LEPUS_IsException(iter)) {
    LEPUSValue error;
  fail_reject:
    error = LEPUS_GetException(ctx);
    func_scope.PushHandle(&error, HANDLE_TYPE_LEPUS_VALUE);
    ret = JS_Call_GC(ctx, resolving_funcs[1], LEPUS_UNDEFINED, 1,
                     reinterpret_cast<LEPUSValueConst *>(&error));
    if (LEPUS_IsException(ret)) goto fail;
    func_scope.ResetHandle(&error, HANDLE_TYPE_LEPUS_VALUE);
  } else {
    next_method = JS_GetPropertyInternal_GC(ctx, iter, JS_ATOM_next, iter, 0);
    if (LEPUS_IsException(next_method)) goto fail_reject;
    values = JS_NewArray_GC(ctx);
    if (LEPUS_IsException(values)) goto fail_reject;
    resolve_element_env = JS_NewArray_GC(ctx);
    if (LEPUS_IsException(resolve_element_env)) goto fail_reject;
    /* remainingElementsCount field */
    if (JS_DefinePropertyValueUint32_GC(
            ctx, resolve_element_env, 0, LEPUS_NewInt32(ctx, 1),
            LEPUS_PROP_CONFIGURABLE | LEPUS_PROP_ENUMERABLE |
                LEPUS_PROP_WRITABLE) < 0)
      goto fail_reject;

    index = 0;
    for (;;) {
      /* XXX: conformance: should close the iterator if error on 'done'
         access, but not on 'value' access */
      item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
      if (LEPUS_IsException(item)) goto fail_reject;
      if (done) break;
      next_promise = JS_Call_GC(ctx, promise_resolve, this_val, 1,
                                reinterpret_cast<LEPUSValueConst *>(&item));
      if (LEPUS_IsException(next_promise)) {
      fail_reject1:
        JS_IteratorClose(ctx, iter, TRUE);
        goto fail_reject;
      }
      resolve_element_data[0] = LEPUS_NewBool(ctx, FALSE);
      resolve_element_data[1] = (LEPUSValueConst)LEPUS_NewInt32(ctx, index);
      resolve_element_data[2] = values;
      resolve_element_data[3] = resolving_funcs[is_promise_any];
      resolve_element_data[4] = resolve_element_env;
      resolve_element =
          JS_NewCFunctionData_GC(ctx, js_promise_all_resolve_element, 1, magic,
                                 5, resolve_element_data);
      if (LEPUS_IsException(resolve_element)) {
        goto fail_reject1;
      }

      if (magic == PROMISE_MAGIC_allSettled) {
        reject_element =
            JS_NewCFunctionData_GC(ctx, js_promise_all_resolve_element, 1,
                                   magic | 4, 5, resolve_element_data);
        if (LEPUS_IsException(reject_element)) {
          goto fail_reject1;
        }
      } else if (magic == PROMISE_MAGIC_any) {
        if (JS_DefinePropertyValueUint32_GC(ctx, values, index, LEPUS_UNDEFINED,
                                            LEPUS_PROP_C_W_E) < 0)
          goto fail_reject1;
        reject_element = resolve_element;
        resolve_element = resolving_funcs[0];
      } else {
        reject_element = resolving_funcs[1];
      }

      if (remainingElementsCount_add(ctx, resolve_element_env, 1) < 0) {
        goto fail_reject1;
      }
      then_args[0] = resolve_element;
      then_args[1] = reject_element;
      ret = JS_InvokeFree(ctx, next_promise, JS_ATOM_then, 2, then_args);
      if (check_exception_free(ctx, ret)) goto fail_reject1;
      index++;
    }

    is_zero = remainingElementsCount_add(ctx, resolve_element_env, -1);
    if (is_zero < 0) goto fail_reject;
    if (is_zero) {
      if (magic == PROMISE_MAGIC_any) {
        LEPUSValue error;
        error = js_aggregate_error_constructor(ctx, values);
        if (LEPUS_IsException(error)) goto fail_reject;
        values = error;
      }
      ret = JS_Call_GC(ctx, resolving_funcs[is_promise_any], LEPUS_UNDEFINED, 1,
                       reinterpret_cast<LEPUSValueConst *>(&values));
      if (check_exception_free(ctx, ret)) goto fail_reject;
    }
  }
done:
  return result_promise;
fail:
  result_promise = LEPUS_EXCEPTION;
  goto done;
}

static LEPUSValue js_promise_race(LEPUSContext *ctx, LEPUSValueConst this_val,
                                  int argc, LEPUSValueConst *argv) {
  HandleScope func_scope(ctx);
  LEPUSValue result_promise, resolving_funcs[2], item = LEPUS_UNDEFINED,
                                                 next_promise = LEPUS_UNDEFINED,
                                                 ret = LEPUS_UNDEFINED;
  func_scope.PushLEPUSValueArrayHandle(resolving_funcs, 2);
  func_scope.PushHandle(&item, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&next_promise, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue next_method = LEPUS_UNDEFINED, iter = LEPUS_UNDEFINED;
  func_scope.PushHandle(&next_method, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&iter, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue promise_resolve = LEPUS_UNDEFINED;
  func_scope.PushHandle(&promise_resolve, HANDLE_TYPE_LEPUS_VALUE);
  BOOL done;

  if (!LEPUS_IsObject(this_val)) return JS_ThrowTypeErrorNotAnObject(ctx);
  result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val);
  if (LEPUS_IsException(result_promise)) return result_promise;
  func_scope.PushHandle(&result_promise, HANDLE_TYPE_LEPUS_VALUE);
  promise_resolve =
      JS_GetPropertyInternal_GC(ctx, this_val, JS_ATOM_resolve, this_val, 0);
  if (LEPUS_IsException(promise_resolve) ||
      check_function(ctx, promise_resolve))
    goto fail_reject;
  iter = JS_GetIterator(ctx, argv[0], FALSE);
  if (LEPUS_IsException(iter)) {
    LEPUSValue error;
  fail_reject:
    error = LEPUS_GetException(ctx);
    func_scope.PushHandle(&error, HANDLE_TYPE_LEPUS_VALUE);
    ret = JS_Call_GC(ctx, resolving_funcs[1], LEPUS_UNDEFINED, 1,
                     reinterpret_cast<LEPUSValueConst *>(&error));
    if (LEPUS_IsException(ret)) goto fail;
    func_scope.ResetHandle(&error, HANDLE_TYPE_LEPUS_VALUE);
  } else {
    next_method = JS_GetPropertyInternal_GC(ctx, iter, JS_ATOM_next, iter, 0);
    if (LEPUS_IsException(next_method)) goto fail_reject;

    for (;;) {
      /* XXX: conformance: should close the iterator if error on 'done'
         access, but not on 'value' access */
      item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
      if (LEPUS_IsException(item)) goto fail_reject;
      if (done) break;
      next_promise = JS_Call_GC(ctx, promise_resolve, this_val, 1,
                                reinterpret_cast<LEPUSValueConst *>(&item));
      if (LEPUS_IsException(next_promise)) {
      fail_reject1:
        JS_IteratorClose(ctx, iter, TRUE);
        goto fail_reject;
      }
      ret = JS_InvokeFree(ctx, next_promise, JS_ATOM_then, 2,
                          reinterpret_cast<LEPUSValueConst *>(resolving_funcs));
      if (check_exception_free(ctx, ret)) goto fail_reject1;
    }
  }
done:
  return result_promise;
fail:
  result_promise = LEPUS_EXCEPTION;
  goto done;
}

static LEPUSValue JS_StructuredClone(LEPUSContext *ctx, LEPUSValue src,
                                     ObjectCloneStateGC &state);
static __exception int perform_promise_then(
    LEPUSContext *ctx, LEPUSValueConst promise, LEPUSValueConst *resolve_reject,
    LEPUSValueConst *cap_resolving_funcs, ObjectCloneStateGC *state) {
  HandleScope func_scope(ctx);
  JSPromiseData *s =
      static_cast<JSPromiseData *>(LEPUS_GetOpaque(promise, JS_CLASS_PROMISE));
  JSPromiseReactionData *rd_array[2], *rd;
  int i, j;

  rd_array[0] = NULL;
  rd_array[1] = NULL;
  for (i = 0; i < 2; i++) {
    LEPUSValueConst handler;
    rd = static_cast<JSPromiseReactionData *>(
        lepus_mallocz(ctx, sizeof(*rd), ALLOC_TAG_JSPromiseReactionData));
    if (!rd) {
      return -1;
    }
    for (j = 0; j < 2; j++) rd->resolving_funcs[j] = cap_resolving_funcs[j];
    handler = resolve_reject[i];
    if (!LEPUS_IsFunction(ctx, handler)) handler = LEPUS_UNDEFINED;
    rd->handler = handler;
    rd_array[i] = rd;
    func_scope.PushHandle(rd_array[i], HANDLE_TYPE_DIR_HEAP_OBJ);
  }

  if (s->promise_state == JS_PROMISE_PENDING) {
    for (i = 0; i < 2; i++)
      list_add_tail(&rd_array[i]->link, &s->promise_reactions[i]);
  } else {
    HandleScope block_scope(ctx->rt);
    LEPUSValueConst args[5];
    block_scope.PushLEPUSValueArrayHandle(args, 5);
    i = s->promise_state - JS_PROMISE_FULFILLED;
    rd = rd_array[i];
    args[0] = rd->resolving_funcs[0];
    args[1] = rd->resolving_funcs[1];
    args[2] = rd->handler;
    args[3] = LEPUS_NewBool(ctx, i);
    args[4] = state ? JS_StructuredClone(ctx, s->promise_result, *state)
                    : s->promise_result;
    JS_EnqueueJob_GC(ctx, promise_reaction_job, 5, args);
  }

  if (s->promise_state == JS_PROMISE_REJECTED) {
    struct list_head *el, *el1;
    list_for_each_safe(el, el1, &ctx->rt->unhandled_rejections) {
      JSUnhandledRejectionEntry *e =
          list_entry(el, JSUnhandledRejectionEntry, link);
      if (!LEPUS_IsNull(e->promise) &&
          LEPUS_GetOpaque(e->promise, JS_CLASS_PROMISE) == s) {
        list_del(el);
        break;
      }
    }
  }

  s->is_handled = TRUE;
  return 0;
}

static LEPUSValue js_promise_then_for_deepcopy(LEPUSContext *ctx,
                                               LEPUSValueConst this_val,
                                               int argc, LEPUSValueConst *argv,
                                               ObjectCloneStateGC *state) {
  LEPUSValue ctor, result_promise, resolving_funcs[2];
  HandleScope func_scope(ctx);
  func_scope.PushLEPUSValueArrayHandle(resolving_funcs, 2);
  JSPromiseData *s;
  int i, ret;

  s = static_cast<JSPromiseData *>(
      LEPUS_GetOpaque2(ctx, this_val, JS_CLASS_PROMISE));
  if (!s) return LEPUS_EXCEPTION;

  ctor = JS_SpeciesConstructor(ctx, this_val, LEPUS_UNDEFINED);
  if (LEPUS_IsException(ctor)) return ctor;
  result_promise = js_new_promise_capability(ctx, resolving_funcs, ctor);
  if (LEPUS_IsException(result_promise)) return result_promise;
  func_scope.PushHandle(&result_promise, HANDLE_TYPE_LEPUS_VALUE);
  ret = perform_promise_then(
      ctx, this_val, argv, reinterpret_cast<LEPUSValueConst *>(resolving_funcs),
      state);
  if (ret) {
    return LEPUS_EXCEPTION;
  }
  return result_promise;
}

static LEPUSValue js_promise_then(LEPUSContext *ctx, LEPUSValueConst this_val,
                                  int argc, LEPUSValueConst *argv) {
  HandleScope func_scope(ctx);
  LEPUSValue ctor, result_promise, resolving_funcs[2];
  func_scope.PushLEPUSValueArrayHandle(resolving_funcs, 2);
  JSPromiseData *s;
  int i, ret;

  s = static_cast<JSPromiseData *>(
      LEPUS_GetOpaque2(ctx, this_val, JS_CLASS_PROMISE));
  if (!s) return LEPUS_EXCEPTION;

  ctor = JS_SpeciesConstructor(ctx, this_val, LEPUS_UNDEFINED);
  if (LEPUS_IsException(ctor)) return ctor;
  func_scope.PushHandle(&ctor, HANDLE_TYPE_LEPUS_VALUE);
  result_promise = js_new_promise_capability(ctx, resolving_funcs, ctor);
  if (LEPUS_IsException(result_promise)) return result_promise;
  func_scope.PushHandle(&result_promise, HANDLE_TYPE_LEPUS_VALUE);
  ret = perform_promise_then(
      ctx, this_val, argv,
      reinterpret_cast<LEPUSValueConst *>(resolving_funcs));
  if (ret) {
    return LEPUS_EXCEPTION;
  }
  return result_promise;
}

static LEPUSValue js_promise_catch(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
  HandleScope func_scope(ctx);
  LEPUSValueConst args[2];
  args[0] = LEPUS_UNDEFINED;
  args[1] = argv[0];
  func_scope.PushLEPUSValueArrayHandle(args, 2, false);
  return JS_Invoke_GC(ctx, this_val, JS_ATOM_then, 2, args);
}

static LEPUSValue js_promise_finally_value_thunk(
    LEPUSContext *ctx, LEPUSValueConst this_val, int argc,
    LEPUSValueConst *argv, int magic, LEPUSValue *func_data) {
  return func_data[0];
}

static LEPUSValue js_promise_finally_thrower(LEPUSContext *ctx,
                                             LEPUSValueConst this_val, int argc,
                                             LEPUSValueConst *argv, int magic,
                                             LEPUSValue *func_data) {
  return LEPUS_Throw(ctx, func_data[0]);
}

static LEPUSValue js_promise_then_finally_func(LEPUSContext *ctx,
                                               LEPUSValueConst this_val,
                                               int argc, LEPUSValueConst *argv,
                                               int magic,
                                               LEPUSValue *func_data) {
  LEPUSValueConst ctor = func_data[0];
  HandleScope func_scope(ctx, &ctor, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst onFinally = func_data[1];
  func_scope.PushHandle(&onFinally, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue res, promise, resolving_funcs[2], ret, then_func;
  func_scope.PushLEPUSValueArrayHandle(resolving_funcs, 2);

  res = JS_Call_GC(ctx, onFinally, LEPUS_UNDEFINED, 0, NULL);
  if (LEPUS_IsException(res)) return res;
  func_scope.PushHandle(&res, HANDLE_TYPE_LEPUS_VALUE);

  promise = js_promise_resolve(ctx, ctor, 1,
                               reinterpret_cast<LEPUSValueConst *>(&res), 0);
  if (LEPUS_IsException(promise)) return promise;
  func_scope.PushHandle(&promise, HANDLE_TYPE_LEPUS_VALUE);
  if (magic == 0) {
    then_func = JS_NewCFunctionData_GC(ctx, js_promise_finally_value_thunk, 0,
                                       0, 1, argv);
  } else {
    then_func =
        JS_NewCFunctionData_GC(ctx, js_promise_finally_thrower, 0, 0, 1, argv);
  }
  if (LEPUS_IsException(then_func)) {
    return then_func;
  }
  func_scope.PushHandle(&then_func, HANDLE_TYPE_LEPUS_VALUE);
  ret = JS_InvokeFree(ctx, promise, JS_ATOM_then, 1,
                      reinterpret_cast<LEPUSValueConst *>(&then_func));
  return ret;
}

static LEPUSValue js_promise_finally(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv) {
  LEPUSValueConst onFinally = argv[0];
  HandleScope func_scope(ctx, &onFinally, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValue ctor, ret;
  LEPUSValue then_funcs[2];
  func_scope.PushLEPUSValueArrayHandle(then_funcs, 2);
  LEPUSValueConst func_data[2];
  func_scope.PushLEPUSValueArrayHandle(func_data, 2);
  int i;

  ctor = JS_SpeciesConstructor(ctx, this_val, LEPUS_UNDEFINED);
  if (LEPUS_IsException(ctor)) return ctor;
  func_scope.PushHandle(&ctor, HANDLE_TYPE_LEPUS_VALUE);
  if (!LEPUS_IsFunction(ctx, onFinally)) {
    then_funcs[0] = onFinally;
    then_funcs[1] = onFinally;
  } else {
    func_data[0] = ctor;
    func_data[1] = onFinally;
    for (i = 0; i < 2; i++) {
      then_funcs[i] = JS_NewCFunctionData_GC(ctx, js_promise_then_finally_func,
                                             1, i, 2, func_data);
      if (LEPUS_IsException(then_funcs[i])) {
        return LEPUS_EXCEPTION;
      }
    }
  }
  ret = JS_Invoke_GC(ctx, this_val, JS_ATOM_then, 2,
                     reinterpret_cast<LEPUSValueConst *>(then_funcs));
  return ret;
}

static const LEPUSCFunctionListEntry js_promise_funcs[] = {
    LEPUS_CFUNC_MAGIC_DEF("resolve", 1, js_promise_resolve, 0),
    LEPUS_CFUNC_MAGIC_DEF("reject", 1, js_promise_resolve, 1),
    LEPUS_CFUNC_MAGIC_DEF("all", 1, js_promise_all, PROMISE_MAGIC_all),
    LEPUS_CFUNC_MAGIC_DEF("allSettled", 1, js_promise_all,
                          PROMISE_MAGIC_allSettled),
    LEPUS_CFUNC_MAGIC_DEF("any", 1, js_promise_all, PROMISE_MAGIC_any),
    LEPUS_CFUNC_DEF("race", 1, js_promise_race),
    // LEPUS_CFUNC_DEF("__newPromiseCapability", 1,
    // js_promise___newPromiseCapability ),
    LEPUS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL),
};

static const LEPUSCFunctionListEntry js_promise_proto_funcs[] = {
    LEPUS_CFUNC_DEF("then", 2, js_promise_then),
    LEPUS_CFUNC_DEF("catch", 1, js_promise_catch),
    LEPUS_CFUNC_DEF("finally", 1, js_promise_finally),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "Promise",
                          LEPUS_PROP_CONFIGURABLE),
};

/* AsyncFunction */
static const LEPUSCFunctionListEntry js_async_function_proto_funcs[] = {
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncFunction",
                          LEPUS_PROP_CONFIGURABLE),
};

static LEPUSValue js_async_from_async_iterator_unwrap(
    LEPUSContext *ctx, LEPUSValueConst this_val, int argc,
    LEPUSValueConst *argv, int magic, LEPUSValue *func_data) {
  return js_create_iterator_result(ctx, func_data[0],
                                   JS_ToBool_GC(ctx, func_data[1]));
}

static LEPUSValue js_async_from_async_iterator_unwrap_func_create(
    LEPUSContext *ctx, LEPUSValueConst value, BOOL done) {
  LEPUSValueConst func_data[2];

  func_data[0] = value;
  func_data[1] = (LEPUSValueConst)LEPUS_NewBool(ctx, done);
  HandleScope func_scope(ctx, &func_data[0], HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&func_data[1], HANDLE_TYPE_LEPUS_VALUE);
  return JS_NewCFunctionData_GC(ctx, js_async_from_async_iterator_unwrap, 0, 0,
                                2, func_data);
}

/* AsyncIteratorPrototype */

static const LEPUSCFunctionListEntry js_async_iterator_proto_funcs[] = {
    LEPUS_CFUNC_DEF("[Symbol.asyncIterator]", 0, js_iterator_proto_iterator),
};

/* AsyncFromSyncIteratorPrototype */

typedef struct JSAsyncFromSyncIteratorData {
  LEPUSValue sync_iter;
  LEPUSValue next_method;
} JSAsyncFromSyncIteratorData;

static LEPUSValue js_async_from_sync_iterator_next(LEPUSContext *ctx,
                                                   LEPUSValueConst this_val,
                                                   int argc,
                                                   LEPUSValueConst *argv,
                                                   int magic) {
  HandleScope func_scope(ctx);
  LEPUSValue promise, resolving_funcs[2], value, err = LEPUS_UNDEFINED,
                                                 method = LEPUS_UNDEFINED;
  func_scope.PushLEPUSValueArrayHandle(resolving_funcs, 2);
  func_scope.PushHandle(&err, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&method, HANDLE_TYPE_LEPUS_VALUE);
  JSAsyncFromSyncIteratorData *s;
  int done;
  int is_reject;

  promise = JS_NewPromiseCapability_GC(ctx, resolving_funcs);
  if (LEPUS_IsException(promise)) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&promise, HANDLE_TYPE_LEPUS_VALUE);
  s = static_cast<JSAsyncFromSyncIteratorData *>(
      LEPUS_GetOpaque(this_val, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR));
  if (!s) {
    LEPUS_ThrowTypeError(ctx, "not an Async-from-Sync Iterator");
    goto reject;
  }

  if (magic == GEN_MAGIC_NEXT) {
    method = s->next_method;
  } else {
    method = JS_GetPropertyInternal_GC(
        ctx, s->sync_iter,
        magic == GEN_MAGIC_RETURN ? JS_ATOM_return : JS_ATOM_throw,
        s->sync_iter, 0);
    if (LEPUS_IsException(method)) goto reject;
    if (LEPUS_IsUndefined(method) || LEPUS_IsNull(method)) {
      if (magic == GEN_MAGIC_RETURN) {
        err = js_create_iterator_result(ctx, argv[0], TRUE);
        is_reject = 0;
      } else {
        err = argv[0];
        is_reject = 1;
      }
      goto done_resolve;
    }
  }
  value = JS_IteratorNext2(ctx, s->sync_iter, method, 1, argv, &done);
  if (LEPUS_IsException(value)) goto reject;
  func_scope.PushHandle(&value, HANDLE_TYPE_LEPUS_VALUE);
  if (done == 2) {
    HandleScope block_scope(ctx->rt);
    LEPUSValue obj = value;
    block_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
    value = JS_IteratorGetCompleteValue(ctx, obj, &done);
    if (LEPUS_IsException(value)) goto reject;
  }

  if (LEPUS_IsException(value)) {
    LEPUSValue res2;
  reject:
    err = LEPUS_GetException(ctx);
    is_reject = 1;
  done_resolve:
    res2 = JS_Call_GC(ctx, resolving_funcs[is_reject], LEPUS_UNDEFINED, 1,
                      reinterpret_cast<LEPUSValueConst *>(&err));
    return promise;
  }
  {
    HandleScope block_scope(ctx->rt);
    LEPUSValue value_wrapper_promise, resolve_reject[2];
    ctx->ptr_handles->PushLEPUSValueArrayHandle(resolve_reject, 2);
    int res;

    value_wrapper_promise =
        js_promise_resolve(ctx, ctx->promise_ctor, 1,
                           reinterpret_cast<LEPUSValueConst *>(&value), 0);
    if (LEPUS_IsException(value_wrapper_promise)) {
      goto reject;
    }
    block_scope.PushHandle(&value_wrapper_promise, HANDLE_TYPE_LEPUS_VALUE);

    resolve_reject[0] =
        js_async_from_async_iterator_unwrap_func_create(ctx, value, done);
    if (LEPUS_IsException(resolve_reject[0])) {
      goto fail;
    }
    resolve_reject[1] = LEPUS_UNDEFINED;

    res = perform_promise_then(
        ctx, value_wrapper_promise,
        reinterpret_cast<LEPUSValueConst *>(resolve_reject),
        reinterpret_cast<LEPUSValueConst *>(resolving_funcs));
    if (res) {
      return LEPUS_EXCEPTION;
    }
  }
  return promise;
fail:
  return LEPUS_EXCEPTION;
}

static const LEPUSCFunctionListEntry js_async_from_sync_iterator_proto_funcs[] =
    {
        LEPUS_CFUNC_MAGIC_DEF("next", 1, js_async_from_sync_iterator_next,
                              GEN_MAGIC_NEXT),
        LEPUS_CFUNC_MAGIC_DEF("return", 1, js_async_from_sync_iterator_next,
                              GEN_MAGIC_RETURN),
        LEPUS_CFUNC_MAGIC_DEF("throw", 1, js_async_from_sync_iterator_next,
                              GEN_MAGIC_THROW),
};

/* AsyncGeneratorFunction */

static const LEPUSCFunctionListEntry js_async_generator_function_proto_funcs[] =
    {
        LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncGeneratorFunction",
                              LEPUS_PROP_CONFIGURABLE),
};

/* AsyncGenerator prototype */

static const LEPUSCFunctionListEntry js_async_generator_proto_funcs[] = {
    LEPUS_CFUNC_MAGIC_DEF("next", 1, js_async_generator_next, GEN_MAGIC_NEXT),
    LEPUS_CFUNC_MAGIC_DEF("return", 1, js_async_generator_next,
                          GEN_MAGIC_RETURN),
    LEPUS_CFUNC_MAGIC_DEF("throw", 1, js_async_generator_next, GEN_MAGIC_THROW),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncGenerator",
                          LEPUS_PROP_CONFIGURABLE),
};

static JSClassShortDef const js_async_class_def[] = {
    {JS_ATOM_Promise, NULL, NULL}, /* JS_CLASS_PROMISE */
    {JS_ATOM_PromiseResolveFunction, NULL,
     NULL}, /* JS_CLASS_PROMISE_RESOLVE_FUNCTION
             */
    {JS_ATOM_PromiseRejectFunction, NULL,
     NULL},                              /* JS_CLASS_PROMISE_REJECT_FUNCTION
                                          */
    {JS_ATOM_AsyncFunction, NULL, NULL}, /* JS_CLASS_ASYNC_FUNCTION */
    {JS_ATOM_AsyncFunctionResolve, NULL,
     NULL}, /* JS_CLASS_ASYNC_FUNCTION_RESOLVE
             */
    {JS_ATOM_AsyncFunctionReject, NULL,
     NULL},                             /* JS_CLASS_ASYNC_FUNCTION_REJECT */
    {JS_ATOM_empty_string, NULL, NULL}, /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR
                                         */
    {JS_ATOM_AsyncGeneratorFunction, NULL,
     NULL}, /* JS_CLASS_ASYNC_GENERATOR_FUNCTION */
    {JS_ATOM_AsyncGenerator, NULL, NULL}, /* JS_CLASS_ASYNC_GENERATOR */
};

void JS_AddIntrinsicPromise_GC(LEPUSContext *ctx) {
  LEPUSRuntime *rt = ctx->rt;
  LEPUSValue obj1;

  if (!LEPUS_IsRegisteredClass(rt, JS_CLASS_PROMISE)) {
    init_class_range(rt, js_async_class_def, JS_CLASS_PROMISE,
                     countof(js_async_class_def));
    rt->class_array[JS_CLASS_PROMISE_RESOLVE_FUNCTION].call =
        js_promise_resolve_function_call;
    rt->class_array[JS_CLASS_PROMISE_REJECT_FUNCTION].call =
        js_promise_resolve_function_call;
    rt->class_array[JS_CLASS_ASYNC_FUNCTION].call = js_async_function_call;
    rt->class_array[JS_CLASS_ASYNC_FUNCTION_RESOLVE].call =
        js_async_function_resolve_call;
    rt->class_array[JS_CLASS_ASYNC_FUNCTION_REJECT].call =
        js_async_function_resolve_call;
    rt->class_array[JS_CLASS_ASYNC_GENERATOR_FUNCTION].call =
        js_async_generator_function_call;
  }

  /* Promise */
  ctx->promise_ctor = JS_NewCConstructor(
      ctx, JS_CLASS_PROMISE, "Promise", js_promise_constructor, 1,
      LEPUS_CFUNC_constructor, 0, LEPUS_UNDEFINED, js_promise_funcs,
      countof(js_promise_funcs), js_promise_proto_funcs,
      countof(js_promise_proto_funcs), 0);

  /* AsyncFunction */
  LEPUSCFunctionType ft = {.generic_magic = js_function_constructor};
  JS_NewCConstructor(ctx, JS_CLASS_ASYNC_FUNCTION, "AsyncFunction", ft.generic,
                     1, LEPUS_CFUNC_constructor_or_func_magic, JS_FUNC_ASYNC,
                     ctx->function_ctor, nullptr, 0,
                     js_async_function_proto_funcs,
                     countof(js_async_function_proto_funcs),
                     JS_NEW_CTOR_NO_GLOBAL | JS_NEW_CTOR_READONLY);

  /* AsyncIteratorPrototype */
  ctx->async_iterator_proto = JS_NewObjectProtoList(
      ctx, ctx->class_proto[JS_CLASS_OBJECT], js_async_iterator_proto_funcs,
      countof(js_async_iterator_proto_funcs));

  /* AsyncFromSyncIteratorPrototype */
  ctx->class_proto[JS_CLASS_ASYNC_FROM_SYNC_ITERATOR] = JS_NewObjectProtoList(
      ctx, ctx->async_iterator_proto, js_async_from_sync_iterator_proto_funcs,
      countof(js_async_from_sync_iterator_proto_funcs));

  /* AsyncGeneratorPrototype */
  ctx->class_proto[JS_CLASS_ASYNC_GENERATOR] = JS_NewObjectProtoList(
      ctx, ctx->async_iterator_proto, js_async_generator_proto_funcs,
      countof(js_async_generator_proto_funcs));

  /* AsyncGeneratorFunction */
  ft.generic_magic = js_function_constructor;
  JS_NewCConstructor(ctx, JS_CLASS_ASYNC_GENERATOR_FUNCTION,
                     "AsyncGeneratorFunction", ft.generic, 1,
                     LEPUS_CFUNC_constructor_or_func_magic,
                     JS_FUNC_ASYNC_GENERATOR, ctx->function_ctor, NULL, 0,
                     js_async_generator_function_proto_funcs,
                     countof(js_async_generator_function_proto_funcs),
                     JS_NEW_CTOR_NO_GLOBAL | JS_NEW_CTOR_READONLY);
  JS_SetConstructor2(ctx, ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION],
                     ctx->class_proto[JS_CLASS_ASYNC_GENERATOR],
                     LEPUS_PROP_CONFIGURABLE, LEPUS_PROP_CONFIGURABLE);
}

/* URI handling */

static int string_get_hex(JSString *p, int k, int n) {
  int c = 0, h;
  while (n-- > 0) {
    if ((h = from_hex(string_get(p, k++))) < 0) return -1;
    c = (c << 4) | h;
  }
  return c;
}

static int isURIReserved(int c) {
  return c < 0x100 &&
         memchr(";/?:@&=+$,#", c, sizeof(";/?:@&=+$,#") - 1) != NULL;
}

static int hex_decode(LEPUSContext *ctx, JSString *p, int k) {
  int c;

  if (k >= p->len || string_get(p, k) != '%')
    return js_throw_URIError(ctx, "expecting %%");
  if (k + 2 >= p->len || (c = string_get_hex(p, k + 1, 2)) < 0)
    return js_throw_URIError(ctx, "expecting hex digit");

  return c;
}

static LEPUSValue js_global_decodeURI(LEPUSContext *ctx,
                                      LEPUSValueConst this_val, int argc,
                                      LEPUSValueConst *argv, int isComponent) {
  LEPUSValue str;
  StringBuffer b_s, *b = &b_s;
  JSString *p;
  int k, c, c1, n, c_min;

  str = JS_ToString_GC(ctx, argv[0]);
  if (LEPUS_IsException(str)) return str;
  HandleScope func_scope(ctx, &str, HANDLE_TYPE_LEPUS_VALUE);

  string_buffer_init(ctx, b, 0);
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);

  p = LEPUS_VALUE_GET_STRING(str);
  for (k = 0; k < p->len;) {
    c = string_get(p, k);
    if (c == '%') {
      c = hex_decode(ctx, p, k);
      if (c < 0) goto fail;
      k += 3;
      if (c < 0x80) {
        if (!isComponent && isURIReserved(c)) {
          c = '%';
          k -= 2;
        }
      } else {
        /* Decode URI-encoded UTF-8 sequence */
        if (c >= 0xc0 && c <= 0xdf) {
          n = 1;
          c_min = 0x80;
          c &= 0x1f;
        } else if (c >= 0xe0 && c <= 0xef) {
          n = 2;
          c_min = 0x800;
          c &= 0xf;
        } else if (c >= 0xf0 && c <= 0xf7) {
          n = 3;
          c_min = 0x10000;
          c &= 0x7;
        } else {
          n = 0;
          c_min = 1;
          c = 0;
        }
        while (n-- > 0) {
          c1 = hex_decode(ctx, p, k);
          if (c1 < 0) goto fail;
          k += 3;
          if ((c1 & 0xc0) != 0x80) {
            c = 0;
            break;
          }
          c = (c << 6) | (c1 & 0x3f);
        }
        if (c < c_min || c > 0x10FFFF) {
          js_throw_URIError(ctx, "malformed UTF-8");
          goto fail;
        }
      }
    } else {
      k++;
    }
    string_buffer_putc(b, c);
  }
  return string_buffer_end(b);

fail:
  b->str = NULL;
  return LEPUS_EXCEPTION;
}

static int isUnescaped(int c) {
  static char const unescaped_chars[] =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
      "abcdefghijklmnopqrstuvwxyz"
      "0123456789"
      "@*_+-./";
  return c < 0x100 && memchr(unescaped_chars, c, sizeof(unescaped_chars) - 1);
}

static int isURIUnescaped(int c, int isComponent) {
  return c < 0x100 &&
         ((c >= 0x61 && c <= 0x7a) || (c >= 0x41 && c <= 0x5a) ||
          (c >= 0x30 && c <= 0x39) ||
          memchr("-_.!~*'()", c, sizeof("-_.!~*'()") - 1) != NULL ||
          (!isComponent && isURIReserved(c)));
}

static int encodeURI_hex(StringBuffer *b, int c) {
  uint8_t buf[6];
  int n = 0;
  const char *hex = "0123456789ABCDEF";

  buf[n++] = '%';
  if (c >= 256) {
    buf[n++] = 'u';
    buf[n++] = hex[(c >> 12) & 15];
    buf[n++] = hex[(c >> 8) & 15];
  }
  buf[n++] = hex[(c >> 4) & 15];
  buf[n++] = hex[(c >> 0) & 15];
  return string_buffer_write8(b, buf, n);
}

static LEPUSValue js_global_encodeURI(LEPUSContext *ctx,
                                      LEPUSValueConst this_val, int argc,
                                      LEPUSValueConst *argv, int isComponent) {
  LEPUSValue str;
  StringBuffer b_s, *b = &b_s;
  JSString *p;
  int k, c, c1;

  str = JS_ToString_GC(ctx, argv[0]);
  if (LEPUS_IsException(str)) return str;
  HandleScope func_scope(ctx, &str, HANDLE_TYPE_LEPUS_VALUE);

  p = LEPUS_VALUE_GET_STRING(str);
  string_buffer_init(ctx, b, p->len);
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);
  for (k = 0; k < p->len;) {
    c = string_get(p, k);
    k++;
    if (isURIUnescaped(c, isComponent)) {
      string_buffer_putc16(b, c);
    } else {
      if (c >= 0xdc00 && c <= 0xdfff) {
        js_throw_URIError(ctx, "invalid character");
        goto fail;
      } else if (c >= 0xd800 && c <= 0xdbff) {
        if (k >= p->len) {
          js_throw_URIError(ctx, "expecting surrogate pair");
          goto fail;
        }
        c1 = string_get(p, k);
        k++;
        if (c1 < 0xdc00 || c1 > 0xdfff) {
          js_throw_URIError(ctx, "expecting surrogate pair");
          goto fail;
        }
        c = (((c & 0x3ff) << 10) | (c1 & 0x3ff)) + 0x10000;
      }
      if (c < 0x80) {
        encodeURI_hex(b, c);
      } else {
        /* XXX: use C UTF-8 conversion ? */
        if (c < 0x800) {
          encodeURI_hex(b, (c >> 6) | 0xc0);
        } else {
          if (c < 0x10000) {
            encodeURI_hex(b, (c >> 12) | 0xe0);
          } else {
            encodeURI_hex(b, (c >> 18) | 0xf0);
            encodeURI_hex(b, ((c >> 12) & 0x3f) | 0x80);
          }
          encodeURI_hex(b, ((c >> 6) & 0x3f) | 0x80);
        }
        encodeURI_hex(b, (c & 0x3f) | 0x80);
      }
    }
  }
  return string_buffer_end(b);

fail:
  b->str = NULL;
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_global_escape(LEPUSContext *ctx, LEPUSValueConst this_val,
                                   int argc, LEPUSValueConst *argv) {
  LEPUSValue str;
  StringBuffer b_s, *b = &b_s;
  JSString *p;
  int i, len, c;

  str = JS_ToString_GC(ctx, argv[0]);
  if (LEPUS_IsException(str)) return str;
  HandleScope func_scope(ctx, &str, HANDLE_TYPE_LEPUS_VALUE);

  p = LEPUS_VALUE_GET_STRING(str);
  string_buffer_init(ctx, b, p->len);
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);
  for (i = 0, len = p->len; i < len; i++) {
    c = string_get(p, i);
    if (isUnescaped(c)) {
      string_buffer_putc16(b, c);
    } else {
      encodeURI_hex(b, c);
    }
  }
  return string_buffer_end(b);
}

static LEPUSValue js_global_unescape(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv) {
  LEPUSValue str;
  StringBuffer b_s, *b = &b_s;
  JSString *p;
  int i, len, c, n;

  str = JS_ToString_GC(ctx, argv[0]);
  if (LEPUS_IsException(str)) return str;
  HandleScope func_scope(ctx, &str, HANDLE_TYPE_LEPUS_VALUE);

  string_buffer_init(ctx, b, 0);
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);
  p = LEPUS_VALUE_GET_STRING(str);
  for (i = 0, len = p->len; i < len; i++) {
    c = string_get(p, i);
    if (c == '%') {
      if (i + 6 <= len && string_get(p, i + 1) == 'u' &&
          (n = string_get_hex(p, i + 2, 4)) >= 0) {
        c = n;
        i += 6 - 1;
      } else if (i + 3 <= len && (n = string_get_hex(p, i + 1, 2)) >= 0) {
        c = n;
        i += 3 - 1;
      }
    }
    string_buffer_putc16(b, c);
  }
  return string_buffer_end(b);
}

/* global object */

static LEPUSCFunctionListEntry js_global_funcs[] = {
    LEPUS_CFUNC_DEF("parseInt", 2, js_parseInt),
    LEPUS_CFUNC_DEF("parseFloat", 1, js_parseFloat),
    LEPUS_CFUNC_DEF("isNaN", 1, js_global_isNaN),
    LEPUS_CFUNC_DEF("isFinite", 1, js_global_isFinite),

    LEPUS_CFUNC_MAGIC_DEF("decodeURI", 1, js_global_decodeURI, 0),
    LEPUS_CFUNC_MAGIC_DEF("decodeURIComponent", 1, js_global_decodeURI, 1),
    LEPUS_CFUNC_MAGIC_DEF("encodeURI", 1, js_global_encodeURI, 0),
    LEPUS_CFUNC_MAGIC_DEF("encodeURIComponent", 1, js_global_encodeURI, 1),
    LEPUS_CFUNC_DEF("escape", 1, js_global_escape),
    LEPUS_CFUNC_DEF("unescape", 1, js_global_unescape),
    LEPUS_PROP_DOUBLE_DEF("Infinity", 1.0 / 0.0, 0),
    LEPUS_PROP_DOUBLE_DEF("NaN", LEPUS_FLOAT64_NAN, 0),
    LEPUS_PROP_UNDEFINED_DEF("undefined", 0),
    LEPUS_CFUNC_DEF("eval", 1, js_global_eval),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "global",
                          LEPUS_PROP_CONFIGURABLE),
    /* for the 'Date' implementation */
    LEPUS_CFUNC_DEF("__date_clock", 0, js___date_clock),
    // LEPUS_CFUNC_DEF("__date_now", 0, js___date_now ),
    // LEPUS_CFUNC_DEF("__date_getTimezoneOffset", 1,
    // js___date_getTimezoneOffset
    // ), LEPUS_CFUNC_DEF("__date_create", 3, js___date_create ),
};

/* Date */

static int64_t math_mod(int64_t a, int64_t b) {
  /* return positive modulo */
  int64_t m = a % b;
  return m + (m < 0) * b;
}

static int64_t floor_div(int64_t a, int64_t b) {
  /* integer division rounding toward -Infinity */
  int64_t m = a % b;
  return (a - (m + (m < 0) * b)) / b;
}

static LEPUSValue js_Date_parse(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv);

static __exception int JS_ThisTimeValue(LEPUSContext *ctx, double *valp,
                                        LEPUSValueConst this_val) {
  if (LEPUS_VALUE_IS_OBJECT(this_val)) {
    LEPUSObject *p = LEPUS_VALUE_GET_OBJ(this_val);
    if (p->class_id == JS_CLASS_DATE && LEPUS_IsNumber(p->u.object_data))
      return JS_ToFloat64_GC(ctx, valp, p->u.object_data);
  }
  LEPUS_ThrowTypeError(ctx, "not a Date object");
  return -1;
}

static LEPUSValue JS_SetThisTimeValue(LEPUSContext *ctx,
                                      LEPUSValueConst this_val, double v) {
  if (LEPUS_VALUE_IS_OBJECT(this_val)) {
    LEPUSObject *p = LEPUS_VALUE_GET_OBJ(this_val);
    if (p->class_id == JS_CLASS_DATE) {
      p->u.object_data = __JS_NewFloat64(ctx, v);
      return p->u.object_data;
    }
  }
  return LEPUS_ThrowTypeError(ctx, "not a Date object");
}

static int64_t days_from_year(int64_t y) {
  return 365 * (y - 1970) + floor_div(y - 1969, 4) - floor_div(y - 1901, 100) +
         floor_div(y - 1601, 400);
}

static int64_t days_in_year(int64_t y) {
  return 365 + !(y % 4) - !(y % 100) + !(y % 400);
}

/* return the year, update days */
static int64_t year_from_days(int64_t *days) {
  int64_t y, d1, nd, d = *days;
  y = floor_div(d * 10000, 3652425) + 1970;
  /* the initial approximation is very good, so only a few
     iterations are necessary */
  for (;;) {
    d1 = d - days_from_year(y);
    if (d1 < 0) {
      y--;
      d1 += days_in_year(y);
    } else {
      nd = days_in_year(y);
      if (d1 < nd) break;
      d1 -= nd;
      y++;
    }
  }
  *days = d1;
  return y;
}

static int const month_days[] = {31, 28, 31, 30, 31, 30,
                                 31, 31, 30, 31, 30, 31};
static char const month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
static char const day_names[] = "SunMonTueWedThuFriSat";

static __exception int get_date_fields(LEPUSContext *ctx, LEPUSValueConst obj,
                                       double fields[9], int is_local,
                                       int force) {
  double dval;
  int64_t d, days, wd, y, i, md, h, m, s, ms, tz = 0;

  if (JS_ThisTimeValue(ctx, &dval, obj)) return -1;

  if (isnan(dval)) {
    if (!force) return FALSE; /* NaN */
    d = 0;                    /* initialize all fields to 0 */
  } else {
    d = dval;
    if (is_local) {
      tz = -getTimezoneOffset(d);
      d += tz * 60000;
    }
  }

  /* result is >= 0, we can use % */
  h = math_mod(d, 86400000);
  days = (d - h) / 86400000;
  ms = h % 1000;
  h = (h - ms) / 1000;
  s = h % 60;
  h = (h - s) / 60;
  m = h % 60;
  h = (h - m) / 60;
  wd = math_mod(days + 4, 7); /* week day */
  y = year_from_days(&days);

  for (i = 0; i < 11; i++) {
    md = month_days[i];
    if (i == 1) md += days_in_year(y) - 365;
    if (days < md) break;
    days -= md;
  }
  fields[0] = y;
  fields[1] = i;
  fields[2] = days + 1;
  fields[3] = h;
  fields[4] = m;
  fields[5] = s;
  fields[6] = ms;
  fields[7] = wd;
  fields[8] = tz;
  return TRUE;
}

static double time_clip(double t) {
  if (t >= -8.64e15 && t <= 8.64e15)
    return trunc(t) + 0.0; /* convert -0 to +0 */
  else
    return LEPUS_FLOAT64_NAN;
}

static double set_date_fields(double fields[], int is_local, int dst_mode = 0) {
  int64_t y;
  double days, h, m1;
  volatile double d;
  int i, m, md;

  m1 = fields[1];
  m = fmod(m1, 12);
  if (m < 0) m += 12;
  y = (int64_t)(fields[0] + floor(m1 / 12));
  days = days_from_year(y);

  for (i = 0; i < m; i++) {
    md = month_days[i];
    if (i == 1) md += days_in_year(y) - 365;
    days += md;
  }
  days += fields[2] - 1;
  h = fields[3] * 3600000 + fields[4] * 60000 + fields[5] * 1000 + fields[6];
  d = days * 86400000;
  d += h;
  if (is_local) d += getTimezoneOffset(d, dst_mode) * 60000;
  return time_clip(d);
}

static LEPUSValue get_date_field(LEPUSContext *ctx, LEPUSValueConst this_val,
                                 int argc, LEPUSValueConst *argv, int magic) {
  // get_date_field(obj, n, is_local)
  double fields[9];
  int res, n, is_local;

  is_local = magic & 0x0F;
  n = (magic >> 4) & 0x0F;
  res = get_date_fields(ctx, this_val, fields, is_local, 0);
  if (res < 0) return LEPUS_EXCEPTION;
  if (!res) return LEPUS_NAN;

  if (magic & 0x100) {  // getYear
    fields[0] -= 1900;
  }
  return LEPUS_NewFloat64(ctx, fields[n]);
}

static LEPUSValue set_date_field(LEPUSContext *ctx, LEPUSValueConst this_val,
                                 int argc, LEPUSValueConst *argv, int magic) {
  // _field(obj, first_field, end_field, args, is_local)
  double fields[9];
  int res, first_field, end_field, is_local, i, n;
  double d, a;

  d = LEPUS_FLOAT64_NAN;
  first_field = (magic >> 8) & 0x0F;
  end_field = (magic >> 4) & 0x0F;
  is_local = magic & 0x0F;

  res = get_date_fields(ctx, this_val, fields, is_local, first_field == 0);
  if (res < 0) return LEPUS_EXCEPTION;
  if (res && argc > 0) {
    n = end_field - first_field;
    if (argc < n) n = argc;
    for (i = 0; i < n; i++) {
      if (JS_ToFloat64_GC(ctx, &a, argv[i])) return LEPUS_EXCEPTION;
      if (!isfinite(a)) goto done;
      fields[first_field + i] = trunc(a);
    }
    d = set_date_fields(fields, is_local);
  }
done:
  return JS_SetThisTimeValue(ctx, this_val, d);
}

/* fmt:
   0: toUTCString: "Tue, 02 Jan 2018 23:04:46 GMT"
   1: toString: "Wed Jan 03 2018 00:05:22 GMT+0100 (CET)"
   2: toISOString: "2018-01-02T23:02:56.927Z"
   3: toLocaleString: "1/2/2018, 11:40:40 PM"
   part: 1=date, 2=time 3=all
   XXX: should use a variant of strftime().
 */

QJS_HIDE
LEPUSValue get_date_string_GC(LEPUSContext *ctx, LEPUSValueConst this_val,
                              int argc, LEPUSValueConst *argv, int magic) {
  // _string(obj, fmt, part)
  char buf[64];
  double fields[9];
  int res, fmt, part, pos;
  int y, mon, d, h, m, s, ms, wd, tz;

  fmt = (magic >> 4) & 0x0F;
  part = magic & 0x0F;

  res = get_date_fields(ctx, this_val, fields, fmt & 1, 0);
  if (res < 0) return LEPUS_EXCEPTION;
  if (!res) {
    if (fmt == 2)
      return LEPUS_ThrowRangeError(ctx, "Date value is NaN");
    else
      return JS_NewString_GC(ctx, "Invalid Date");
  }

  y = fields[0];
  mon = fields[1];
  d = fields[2];
  h = fields[3];
  m = fields[4];
  s = fields[5];
  ms = fields[6];
  wd = fields[7];
  tz = fields[8];

  pos = 0;

  if (part & 1) { /* date part */
    switch (fmt) {
      case 0:
        pos += snprintf(buf + pos, sizeof(buf) - pos, "%.3s, %02d %.3s %0*d ",
                        day_names + wd * 3, d, month_names + mon * 3,
                        4 + (y < 0), y);
        break;
      case 1:
        pos += snprintf(buf + pos, sizeof(buf) - pos, "%.3s %.3s %02d %0*d",
                        day_names + wd * 3, month_names + mon * 3, d,
                        4 + (y < 0), y);
        if (part == 3) {
          buf[pos++] = ' ';
        }
        break;
      case 2:
        if (y >= 0 && y <= 9999) {
          pos += snprintf(buf + pos, sizeof(buf) - pos, "%04d", y);
        } else {
          pos += snprintf(buf + pos, sizeof(buf) - pos, "%+07d", y);
        }
        pos +=
            snprintf(buf + pos, sizeof(buf) - pos, "-%02d-%02dT", mon + 1, d);
        break;
      case 3:
        pos += snprintf(buf + pos, sizeof(buf) - pos, "%02d/%02d/%0*d", mon + 1,
                        d, 4 + (y < 0), y);
        if (part == 3) {
          buf[pos++] = ',';
          buf[pos++] = ' ';
        }
        break;
    }
  }
  if (part & 2) { /* time part */
    switch (fmt) {
      case 0:
        pos += snprintf(buf + pos, sizeof(buf) - pos, "%02d:%02d:%02d GMT", h,
                        m, s);
        break;
      case 1:
        pos += snprintf(buf + pos, sizeof(buf) - pos, "%02d:%02d:%02d GMT", h,
                        m, s);
        if (tz < 0) {
          buf[pos++] = '-';
          tz = -tz;
        } else {
          buf[pos++] = '+';
        }
        /* tz is >= 0, can use % */
        pos += snprintf(buf + pos, sizeof(buf) - pos, "%02d%02d", tz / 60,
                        tz % 60);
        /* XXX: tack the time zone code? */
        break;
      case 2:
        pos += snprintf(buf + pos, sizeof(buf) - pos, "%02d:%02d:%02d.%03dZ", h,
                        m, s, ms);
        break;
      case 3:
        pos += snprintf(buf + pos, sizeof(buf) - pos, "%02d:%02d:%02d %cM",
                        (h <= 12 ? h : h - 12), m, s, (h < 12) ? 'A' : 'P');
        break;
    }
  }
  return JS_NewStringLen_GC(ctx, buf, pos);
}

/* OS dependent: return the UTC time in ms since 1970. */
static LEPUSValue js_date_constructor(LEPUSContext *ctx,
                                      LEPUSValueConst new_target, int argc,
                                      LEPUSValueConst *argv) {
  // Date(y, mon, d, h, m, s, ms)
  LEPUSValue rv = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &rv, HANDLE_TYPE_LEPUS_VALUE);
  int i, n;
  double a, val;

  if (LEPUS_IsUndefined(new_target)) {
    /* invoked as function */
    argc = 0;
  }
  n = argc;
  if (n == 0) {
    val = date_now();
  } else if (n == 1) {
    HandleScope block_scope(ctx->rt);
    LEPUSValue v = LEPUS_UNDEFINED, dv = LEPUS_UNDEFINED;
    block_scope.PushHandle(&v, HANDLE_TYPE_LEPUS_VALUE);
    block_scope.PushHandle(&dv, HANDLE_TYPE_LEPUS_VALUE);
    if (LEPUS_VALUE_IS_OBJECT(argv[0])) {
      LEPUSObject *p = LEPUS_VALUE_GET_OBJ(argv[0]);
      if (p->class_id == JS_CLASS_DATE && LEPUS_IsNumber(p->u.object_data)) {
        if (JS_ToFloat64_GC(ctx, &val, p->u.object_data))
          return LEPUS_EXCEPTION;
        val = time_clip(val);
        goto has_val;
      }
    }
    v = JS_ToPrimitive(ctx, argv[0], HINT_NONE);
    if (LEPUS_IsString(v)) {
      dv = js_Date_parse(ctx, LEPUS_UNDEFINED, 1, (LEPUSValueConst *)&v);
      if (LEPUS_IsException(dv)) return LEPUS_EXCEPTION;
      if (JS_ToFloat64Free(ctx, &val, dv)) return LEPUS_EXCEPTION;
    } else {
      if (JS_ToFloat64Free(ctx, &val, v)) return LEPUS_EXCEPTION;
    }
    val = time_clip(val);
  } else {
    double fields[] = {0, 0, 1, 0, 0, 0, 0};
    if (n > 7) n = 7;
    for (i = 0; i < n; i++) {
      if (JS_ToFloat64_GC(ctx, &a, argv[i])) return LEPUS_EXCEPTION;
      if (!isfinite(a)) break;
      fields[i] = trunc(a);
      if (i == 0 && fields[0] >= 0 && fields[0] < 100) fields[0] += 1900;
    }
    val = (i == n) ? set_date_fields(fields, 1) : LEPUS_FLOAT64_NAN;
  }
has_val:
#if 0
    LEPUSValueConst args[3];
    args[0] = new_target;
    args[1] = ctx->class_proto[JS_CLASS_DATE];
    args[2] = __JS_NewFloat64(ctx, val);
    rv = js___date_create(ctx, LEPUS_UNDEFINED, 3, args);
#else
  rv = js_create_from_ctor_GC(ctx, new_target, JS_CLASS_DATE);
  if (!LEPUS_IsException(rv))
    JS_SetObjectData(ctx, rv, __JS_NewFloat64(ctx, val));
#endif
  if (!LEPUS_IsException(rv) && LEPUS_IsUndefined(new_target)) {
    /* invoked as a function, return (new Date()).toString(); */
    LEPUSValue s;
    s = get_date_string_GC(ctx, rv, 0, NULL, 0x13);
    rv = s;
  }
  return rv;
}

static LEPUSValue js_Date_UTC(LEPUSContext *ctx, LEPUSValueConst this_val,
                              int argc, LEPUSValueConst *argv) {
  // UTC(y, mon, d, h, m, s, ms)
  double fields[] = {0, 0, 1, 0, 0, 0, 0};
  int i, n;
  double a;

  n = argc;
  if (n == 0) return LEPUS_NAN;
  if (n > 7) n = 7;
  for (i = 0; i < n; i++) {
    if (JS_ToFloat64_GC(ctx, &a, argv[i])) return LEPUS_EXCEPTION;
    if (!isfinite(a)) return LEPUS_NAN;
    fields[i] = trunc(a);
    if (i == 0 && fields[0] >= 0 && fields[0] < 100) fields[0] += 1900;
  }
  return LEPUS_NewFloat64(ctx, set_date_fields(fields, 0));
}

static void string_skip_spaces(JSString *sp, int *pp) {
  while (*pp < sp->len && string_get(sp, *pp) == ' ') *pp += 1;
}

static void string_skip_non_spaces(JSString *sp, int *pp) {
  while (*pp < sp->len && string_get(sp, *pp) != ' ') *pp += 1;
}

/* parse a numeric field */
static int string_get_field(JSString *sp, int *pp, int64_t *pval) {
  int64_t v = 0;
  int c, p = *pp;

  /* skip non digits, should only skip spaces? */
  while (p < sp->len) {
    c = string_get(sp, p);
    if (c >= '0' && c <= '9') break;
    p++;
  }
  if (p >= sp->len) return -1;
  while (p < sp->len) {
    c = string_get(sp, p);
    if (!(c >= '0' && c <= '9')) break;
    v = v * 10 + c - '0';
    p++;
  }
  *pval = v;
  *pp = p;
  return 0;
}

/* parse a fixed width numeric field */
static int string_get_digits(JSString *sp, int *pp, int n, int64_t *pval) {
  int64_t v = 0;
  int i, c, p = *pp;

  for (i = 0; i < n; i++) {
    if (p >= sp->len) return -1;
    c = string_get(sp, p);
    if (!(c >= '0' && c <= '9')) return -1;
    v = v * 10 + c - '0';
    p++;
  }
  *pval = v;
  *pp = p;
  return 0;
}

/* parse a signed numeric field */
static int string_get_signed_field(JSString *sp, int *pp, int64_t *pval) {
  int sgn, res;

  if (*pp >= sp->len) return -1;

  sgn = string_get(sp, *pp);
  if (sgn == '-' || sgn == '+') *pp += 1;

  res = string_get_field(sp, pp, pval);
  if (res == 0 && sgn == '-') *pval = -*pval;
  return res;
}

static int find_abbrev(JSString *sp, int p, const char *list, int count) {
  int n, i;

  if (p + 3 <= sp->len) {
    for (n = 0; n < count; n++) {
      for (i = 0; i < 3; i++) {
        if (string_get(sp, p + i) != month_names[n * 3 + i]) goto next;
      }
      return n;
    next:;
    }
  }
  return -1;
}

static int string_get_month(JSString *sp, int *pp, int64_t *pval) {
  int n;

  string_skip_spaces(sp, pp);
  n = find_abbrev(sp, *pp, month_names, 12);
  if (n < 0) return -1;

  *pval = n;
  *pp += 3;
  return 0;
}

static LEPUSValue js_Date_parse(LEPUSContext *ctx, LEPUSValueConst this_val,
                                int argc, LEPUSValueConst *argv) {
  // parse(s)
  LEPUSValue s, rv;
  int64_t fields[] = {0, 1, 1, 0, 0, 0, 0};
  double fields1[7];
  int64_t tz, hh, mm;
  double d;
  int p, i, c, sgn;
  JSString *sp;
  BOOL is_local = FALSE;  //  date-time forms are interpreted as a local time

  rv = LEPUS_NAN;
  HandleScope func_scope(ctx, &rv, HANDLE_TYPE_LEPUS_VALUE);
  struct tm info {};
  time_t t;
  int dst_mode = 0;

  s = JS_ToString_GC(ctx, argv[0]);
  if (LEPUS_IsException(s)) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&s, HANDLE_TYPE_LEPUS_VALUE);

  sp = LEPUS_VALUE_GET_STRING(s);
  p = 0;
  if (p < sp->len &&
      (((c = string_get(sp, p)) >= '0' && c <= '9') || c == '+' || c == '-')) {
    /* ISO format */
    /* year field can be negative */
    /* XXX: could be stricter */
    if (string_get_signed_field(sp, &p, &fields[0])) goto done;

    for (i = 1; i < 6; i++) {
      if (string_get_field(sp, &p, &fields[i])) break;
    }
    is_local = (i > 3);  // more than 3 fields -> date-time form
    if (i == 6 && p < sp->len && string_get(sp, p) == '.') {
      /* parse milliseconds as a fractional part, round to nearest */
      /* XXX: the spec does not indicate which rounding should be used */
      int mul = 1000, ms = 0;
      while (++p < sp->len) {
        int c = string_get(sp, p);
        if (!(c >= '0' && c <= '9')) break;
        if (mul == 1 && c >= '5') ms += 1;
        ms += (c - '0') * (mul /= 10);
      }
      fields[6] = ms;
    }
    fields[1] -= 1;

    /* parse the time zone offset if present: [+-]HH:mm */
    tz = 0;
    if (p < sp->len &&
        ((sgn = string_get(sp, p)) == '+' || sgn == '-' || sgn == 'Z')) {
      if (sgn != 'Z') {
        if (string_get_field(sp, &p, &hh)) goto done;
        if (string_get_field(sp, &p, &mm)) goto done;
        tz = hh * 60 + mm;
        if (sgn == '-') tz = -tz;
      }
      is_local = FALSE;  // UTC offset representation, use offset
    }
  } else {
    /* toString or toUTCString format */
    /* skip the day of the week */
    string_skip_non_spaces(sp, &p);
    string_skip_spaces(sp, &p);
    if (p >= sp->len) goto done;
    c = string_get(sp, p);
    if (c >= '0' && c <= '9') {
      /* day of month first */
      if (string_get_field(sp, &p, &fields[2])) goto done;
      if (string_get_month(sp, &p, &fields[1])) goto done;
    } else {
      /* month first */
      if (string_get_month(sp, &p, &fields[1])) goto done;
      if (string_get_field(sp, &p, &fields[2])) goto done;
    }
    string_skip_spaces(sp, &p);
    if (string_get_signed_field(sp, &p, &fields[0])) goto done;

    /* hour, min, seconds */
    for (i = 0; i < 3; i++) {
      if (string_get_field(sp, &p, &fields[3 + i])) goto done;
    }
    // XXX: parse optional milliseconds?

    /* parse the time zone offset if present: [+-]HHmm */
    tz = 0;
    for (tz = 0; p < sp->len; p++) {
      sgn = string_get(sp, p);
      if (sgn == '+' || sgn == '-') {
        p++;
        if (string_get_digits(sp, &p, 2, &hh)) goto done;
        if (string_get_digits(sp, &p, 2, &mm)) goto done;
        tz = hh * 60 + mm;
        if (sgn == '-') tz = -tz;
        break;
      }
    }
  }
  for (i = 0; i < 7; i++) fields1[i] = fields[i];
  info.tm_year = fields[0] - 1900;
  info.tm_mon = fields[1];
  info.tm_mday = fields[2];
  info.tm_hour = fields[3];
  info.tm_min = 0;
  info.tm_isdst = 1;
  t = mktime(&info);
  dst_mode = info.tm_isdst == 1 ? 1 : 2;  // 1: dst, 2: no dst
  d = set_date_fields(fields1, is_local, dst_mode) - tz * 60000;
  rv = __JS_NewFloat64(ctx, d);

done:
  return rv;
}

static LEPUSValue js_Date_now(LEPUSContext *ctx, LEPUSValueConst this_val,
                              int argc, LEPUSValueConst *argv) {
  // now()
  return JS_NewInt64_GC(ctx, date_now());
}

static LEPUSValue js_date_Symbol_toPrimitive(LEPUSContext *ctx,
                                             LEPUSValueConst this_val, int argc,
                                             LEPUSValueConst *argv) {
  // Symbol_toPrimitive(hint)
  LEPUSValueConst obj = this_val;
  JSAtom hint = JS_ATOM_NULL;
  int hint_num;

  if (!LEPUS_IsObject(obj)) return JS_ThrowTypeErrorNotAnObject(ctx);

  if (LEPUS_IsString(argv[0])) {
    hint = js_value_to_atom_gc(ctx, argv[0]);
    if (hint == JS_ATOM_NULL) return LEPUS_EXCEPTION;
  }
  switch (hint) {
    case JS_ATOM_number:
    case JS_ATOM_integer:
      hint_num = HINT_NUMBER;
      break;
    case JS_ATOM_string:
    case JS_ATOM_default:
      hint_num = HINT_STRING;
      break;
    default:
      return LEPUS_ThrowTypeError(ctx, "invalid hint");
  }
  return JS_ToPrimitive(ctx, obj, hint_num | HINT_FORCE_ORDINARY);
}

static LEPUSValue js_date_getTimezoneOffset(LEPUSContext *ctx,
                                            LEPUSValueConst this_val, int argc,
                                            LEPUSValueConst *argv) {
  // getTimezoneOffset()
  double v;

  if (JS_ThisTimeValue(ctx, &v, this_val)) return LEPUS_EXCEPTION;
  if (isnan(v))
    return LEPUS_NAN;
  else
    return JS_NewInt64_GC(ctx, getTimezoneOffset((int64_t)trunc(v)));
}

static LEPUSValue js_date_getTime(LEPUSContext *ctx, LEPUSValueConst this_val,
                                  int argc, LEPUSValueConst *argv) {
  // getTime()
  double v;

  if (JS_ThisTimeValue(ctx, &v, this_val)) return LEPUS_EXCEPTION;
  return __JS_NewFloat64(ctx, v);
}

static LEPUSValue js_date_setTime(LEPUSContext *ctx, LEPUSValueConst this_val,
                                  int argc, LEPUSValueConst *argv) {
  // setTime(v)
  double v;

  if (JS_ThisTimeValue(ctx, &v, this_val) || JS_ToFloat64_GC(ctx, &v, argv[0]))
    return LEPUS_EXCEPTION;
  return JS_SetThisTimeValue(ctx, this_val, time_clip(v));
}

static LEPUSValue js_date_setYear(LEPUSContext *ctx, LEPUSValueConst this_val,
                                  int argc, LEPUSValueConst *argv) {
  // setYear(y)
  double y;
  LEPUSValueConst args[1];

  if (JS_ThisTimeValue(ctx, &y, this_val) || JS_ToFloat64_GC(ctx, &y, argv[0]))
    return LEPUS_EXCEPTION;
  y = +y;
  if (isfinite(y)) {
    y = trunc(y);
    if (y >= 0 && y < 100) y += 1900;
  }
  args[0] = __JS_NewFloat64(ctx, y);
  return set_date_field(ctx, this_val, 1, args, 0x011);
}

static LEPUSValue js_date_toJSON(LEPUSContext *ctx, LEPUSValueConst this_val,
                                 int argc, LEPUSValueConst *argv) {
  // toJSON(key)
  LEPUSValue obj, tv, method, rv;
  double d;

  rv = LEPUS_EXCEPTION;
  tv = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &rv, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&tv, HANDLE_TYPE_LEPUS_VALUE);

  obj = JS_ToObject_GC(ctx, this_val);
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  tv = JS_ToPrimitive(ctx, obj, HINT_NUMBER);
  if (LEPUS_IsException(tv)) goto exception;
  if (LEPUS_IsNumber(tv)) {
    if (JS_ToFloat64_GC(ctx, &d, tv) < 0) goto exception;
    if (!isfinite(d)) {
      rv = LEPUS_NULL;
      goto done;
    }
  }
  method = JS_GetPropertyStr_GC(ctx, obj, "toISOString");
  if (LEPUS_IsException(method)) goto exception;
  if (!LEPUS_IsFunction(ctx, method)) {
    LEPUS_ThrowTypeError(ctx, "object needs toISOString method");
    goto exception;
  }
  rv = JS_CallFree_GC(ctx, method, obj, 0, NULL);
exception:
done:
  return rv;
}

static const LEPUSCFunctionListEntry js_date_funcs[] = {
    LEPUS_CFUNC_DEF("now", 0, js_Date_now),
    LEPUS_CFUNC_DEF("parse", 1, js_Date_parse),
    LEPUS_CFUNC_DEF("UTC", 7, js_Date_UTC),
};

static const LEPUSCFunctionListEntry js_date_proto_funcs[] = {
    LEPUS_CFUNC_DEF("valueOf", 0, js_date_getTime),
    LEPUS_CFUNC_MAGIC_DEF("toString", 0, get_date_string_GC, 0x13),
    LEPUS_CFUNC_DEF("[Symbol.toPrimitive]", 1, js_date_Symbol_toPrimitive),
    LEPUS_CFUNC_MAGIC_DEF("toUTCString", 0, get_date_string_GC, 0x03),
    LEPUS_ALIAS_DEF("toGMTString", "toUTCString"),
    LEPUS_CFUNC_MAGIC_DEF("toISOString", 0, get_date_string_GC, 0x23),
    LEPUS_CFUNC_MAGIC_DEF("toDateString", 0, get_date_string_GC, 0x11),
    LEPUS_CFUNC_MAGIC_DEF("toTimeString", 0, get_date_string_GC, 0x12),
    LEPUS_CFUNC_MAGIC_DEF("toLocaleString", 0, get_date_string_GC, 0x33),
    LEPUS_CFUNC_MAGIC_DEF("toLocaleDateString", 0, get_date_string_GC, 0x31),
    LEPUS_CFUNC_MAGIC_DEF("toLocaleTimeString", 0, get_date_string_GC, 0x32),
    LEPUS_CFUNC_DEF("getTimezoneOffset", 0, js_date_getTimezoneOffset),
    LEPUS_CFUNC_DEF("getTime", 0, js_date_getTime),
    LEPUS_CFUNC_MAGIC_DEF("getYear", 0, get_date_field, 0x101),
    LEPUS_CFUNC_MAGIC_DEF("getFullYear", 0, get_date_field, 0x01),
    LEPUS_CFUNC_MAGIC_DEF("getUTCFullYear", 0, get_date_field, 0x00),
    LEPUS_CFUNC_MAGIC_DEF("getMonth", 0, get_date_field, 0x11),
    LEPUS_CFUNC_MAGIC_DEF("getUTCMonth", 0, get_date_field, 0x10),
    LEPUS_CFUNC_MAGIC_DEF("getDate", 0, get_date_field, 0x21),
    LEPUS_CFUNC_MAGIC_DEF("getUTCDate", 0, get_date_field, 0x20),
    LEPUS_CFUNC_MAGIC_DEF("getHours", 0, get_date_field, 0x31),
    LEPUS_CFUNC_MAGIC_DEF("getUTCHours", 0, get_date_field, 0x30),
    LEPUS_CFUNC_MAGIC_DEF("getMinutes", 0, get_date_field, 0x41),
    LEPUS_CFUNC_MAGIC_DEF("getUTCMinutes", 0, get_date_field, 0x40),
    LEPUS_CFUNC_MAGIC_DEF("getSeconds", 0, get_date_field, 0x51),
    LEPUS_CFUNC_MAGIC_DEF("getUTCSeconds", 0, get_date_field, 0x50),
    LEPUS_CFUNC_MAGIC_DEF("getMilliseconds", 0, get_date_field, 0x61),
    LEPUS_CFUNC_MAGIC_DEF("getUTCMilliseconds", 0, get_date_field, 0x60),
    LEPUS_CFUNC_MAGIC_DEF("getDay", 0, get_date_field, 0x71),
    LEPUS_CFUNC_MAGIC_DEF("getUTCDay", 0, get_date_field, 0x70),
    LEPUS_CFUNC_DEF("setTime", 1, js_date_setTime),
    LEPUS_CFUNC_MAGIC_DEF("setMilliseconds", 1, set_date_field, 0x671),
    LEPUS_CFUNC_MAGIC_DEF("setUTCMilliseconds", 1, set_date_field, 0x670),
    LEPUS_CFUNC_MAGIC_DEF("setSeconds", 2, set_date_field, 0x571),
    LEPUS_CFUNC_MAGIC_DEF("setUTCSeconds", 2, set_date_field, 0x570),
    LEPUS_CFUNC_MAGIC_DEF("setMinutes", 3, set_date_field, 0x471),
    LEPUS_CFUNC_MAGIC_DEF("setUTCMinutes", 3, set_date_field, 0x470),
    LEPUS_CFUNC_MAGIC_DEF("setHours", 4, set_date_field, 0x371),
    LEPUS_CFUNC_MAGIC_DEF("setUTCHours", 4, set_date_field, 0x370),
    LEPUS_CFUNC_MAGIC_DEF("setDate", 1, set_date_field, 0x231),
    LEPUS_CFUNC_MAGIC_DEF("setUTCDate", 1, set_date_field, 0x230),
    LEPUS_CFUNC_MAGIC_DEF("setMonth", 2, set_date_field, 0x131),
    LEPUS_CFUNC_MAGIC_DEF("setUTCMonth", 2, set_date_field, 0x130),
    LEPUS_CFUNC_DEF("setYear", 1, js_date_setYear),
    LEPUS_CFUNC_MAGIC_DEF("setFullYear", 3, set_date_field, 0x031),
    LEPUS_CFUNC_MAGIC_DEF("setUTCFullYear", 3, set_date_field, 0x030),
    LEPUS_CFUNC_DEF("toJSON", 1, js_date_toJSON),
};

void JS_AddIntrinsicDate_GC(LEPUSContext *ctx) {
  /* Date */
  JS_NewCConstructor(ctx, JS_CLASS_DATE, "Date", js_date_constructor, 7,
                     LEPUS_CFUNC_constructor_or_func, 0, LEPUS_UNDEFINED,
                     js_date_funcs, countof(js_date_funcs), js_date_proto_funcs,
                     countof(js_date_proto_funcs), 0);
}

/* eval */

void JS_AddIntrinsicEval_GC(LEPUSContext *ctx) {
#ifndef NO_QUICKJS_COMPILER
  ctx->eval_internal = __JS_EvalInternal_GC;
#else
  ctx->eval_internal = NULL;
#endif
}

/* BigInt */
static LEPUSValue JS_ToBigIntCtorFree(LEPUSContext *ctx, LEPUSValue val) {
  int64_t tag;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);

redo:
  tag = LEPUS_VALUE_GET_NORM_TAG(val);
  switch (tag) {
    case LEPUS_TAG_BOOL:
      val = LEPUS_NewBigInt64(ctx, LEPUS_VALUE_GET_BOOL(val));
      break;
    case LEPUS_TAG_INT:
      val = LEPUS_NewBigInt64(ctx, LEPUS_VALUE_GET_INT(val));
      break;
    case LEPUS_TAG_BIG_INT:
      break;
    case LEPUS_TAG_FLOAT64: {
      double d = LEPUS_VALUE_GET_FLOAT64(val);
      JSBigInt *r;
      int res;
      r = js_bigint_from_float64(ctx, &res, d);
      if (!r) {
        if (res == 0) {
          val = LEPUS_EXCEPTION;
        } else if (res == 1) {
          val = LEPUS_ThrowRangeError(
              ctx, "cannot convert to BigInt: not an integer");
        } else {
          val = LEPUS_ThrowRangeError(
              ctx, "cannot convert NaN or Infinity to BigINt");
        }
      } else {
        val = JS_CompactBigInt(ctx, r);
      }
    } break;
    case LEPUS_TAG_STRING:
    case LEPUS_TAG_SEPARABLE_STRING:
      val = JS_StringToBigIntErr(ctx, val);
      break;
    case LEPUS_TAG_OBJECT:
      val = JS_ToPrimitiveFree_GC(ctx, val, HINT_NUMBER);
      if (LEPUS_IsException(val)) break;
      goto redo;
    case LEPUS_TAG_NULL:
    case LEPUS_TAG_UNDEFINED:
    default:
      return LEPUS_ThrowTypeError(ctx, "cannot convert to BigInt");
  }
  return val;
}

static LEPUSValue js_bigint_constructor(LEPUSContext *ctx,
                                        LEPUSValue new_target, int argc,
                                        LEPUSValueConst *argv) {
  if (!LEPUS_IsUndefined(new_target)) {
    return LEPUS_ThrowTypeError(ctx, "not a constructor");
  }
  return JS_ToBigIntCtorFree(ctx, argv[0]);
}

static LEPUSValue js_thisBigIntValue(LEPUSContext *ctx,
                                     LEPUSValueConst this_val) {
  if (JS_IsBigInt(ctx, this_val)) return this_val;

  if (LEPUS_VALUE_GET_TAG(this_val) == LEPUS_TAG_OBJECT) {
    LEPUSObject *p = LEPUS_VALUE_GET_OBJ(this_val);
    if (p->class_id == JS_CLASS_BIG_INT) {
      /* XXX: may relax the check in case the object comes from
         bignum mode */
      if (JS_IsBigInt(ctx, p->u.object_data)) return p->u.object_data;
    }
  }
  return LEPUS_ThrowTypeError(ctx, "not a BigInt");
}

static LEPUSValue js_bigint_toString(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv) {
  // trace_gc, todo
  LEPUSValue val;
  int base;
  LEPUSValue ret;

  val = js_thisBigIntValue(ctx, this_val);
  if (LEPUS_IsException(val)) return val;
  if (argc == 0 || LEPUS_IsUndefined(argv[0])) {
    base = 10;
  } else {
    base = js_get_radix(ctx, argv[0]);
    if (base < 0) goto fail;
  }
  ret = js_bigint_to_string1(ctx, val, base);
  return ret;
fail:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_bigint_valueOf(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
  return js_thisBigIntValue(ctx, this_val);
}

static LEPUSValue js_bigint_asUintN(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv,
                                    int asIntN) {
  uint64_t bits;
  LEPUSValue res, a;
  res = a = LEPUS_UNDEFINED;

  if (LEPUS_ToIndex(ctx, &bits, argv[0])) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, &res, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&a, HANDLE_TYPE_LEPUS_VALUE);

  a = JS_ToBigInt(ctx, argv[1]);
  if (LEPUS_IsException(a)) return LEPUS_EXCEPTION;
  JSBigInt *r, *p = LEPUS_VALUE_GET_BIGINT(a);
  if (bits == 0) {
    return LEPUS_NewBigInt64(ctx, 0);
  }
  if (bits >= p->len * JS_LIMB_BITS) {
    res = a;
  } else {
    int len, shift, i;
    js_limb_t v;
    len = (bits + JS_LIMB_BITS - 1) / JS_LIMB_BITS;
    r = js_bigint_new(ctx, len);
    if (!r) {
      return LEPUS_EXCEPTION;
    }
    r->len = len;
    for (i = 0; i < len - 1; i++) r->tab[i] = p->tab[i];
    shift = (-bits) & (JS_LIMB_BITS - 1);
    /* 0 <= shift <= JS_LIMB_BITS - 1 */
    v = p->tab[len - 1] << shift;
    if (asIntN)
      v = (js_slimb_t)v >> shift;
    else
      v = v >> shift;
    r->tab[len - 1] = v;
    r = js_bigint_normalize(ctx, r);
    res = JS_CompactBigInt(ctx, r);
  }

  return res;
}

static const LEPUSCFunctionListEntry js_bigint_funcs[] = {
    LEPUS_CFUNC_MAGIC_DEF("asUintN", 2, js_bigint_asUintN, 0),
    LEPUS_CFUNC_MAGIC_DEF("asIntN", 2, js_bigint_asUintN, 1),
};

static const LEPUSCFunctionListEntry js_bigint_proto_funcs[] = {
    LEPUS_CFUNC_DEF("toString", 0, js_bigint_toString),
    LEPUS_CFUNC_DEF("valueOf", 0, js_bigint_valueOf),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "BigInt",
                          LEPUS_PROP_CONFIGURABLE),
};

QJS_STATIC void JS_AddIntrinsicBigInt(LEPUSContext *ctx) {
  JS_NewCConstructor(ctx, JS_CLASS_BIG_INT, "BigInt", js_bigint_constructor, 1,
                     LEPUS_CFUNC_constructor_or_func, 0, LEPUS_UNDEFINED,
                     js_bigint_funcs, countof(js_bigint_funcs),
                     js_bigint_proto_funcs, countof(js_bigint_proto_funcs), 0);
}

/* Minimum amount of objects to be able to compile code and display
   error messages. No JSAtom should be allocated by this function. */
QJS_STATIC void JS_AddIntrinsicBasicObjects_GC(LEPUSContext *ctx) {
  LEPUSValue proto = LEPUS_UNDEFINED, obj = LEPUS_UNDEFINED;
  LEPUSCFunctionType ft;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);

  int i;

  ctx->class_proto[JS_CLASS_OBJECT] = JS_NewObjectProtoClassAlloc(
      ctx, LEPUS_NULL, JS_CLASS_OBJECT, countof(js_object_proto_funcs) + 1);

  /* 3 properties: constructor, */
  /* 2 more properties: caller and arguments */
  ctx->function_proto =
      JS_NewCFunction3(ctx, js_function_proto, "", 0, LEPUS_CFUNC_generic, 0,
                       ctx->class_proto[JS_CLASS_OBJECT],
                       countof(js_function_proto_funcs) + 3 + 2);
  ctx->class_proto[JS_CLASS_BYTECODE_FUNCTION] = ctx->function_proto;
  ctx->class_proto[JS_CLASS_ERROR] = JS_NewObject_GC(ctx);
  ctx->global_obj = JS_NewObjectProtoClassAlloc(
      ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT, 64);

  ctx->global_var_obj =
      JS_NewObjectProtoClassAlloc(ctx, LEPUS_NULL, JS_CLASS_OBJECT, 16);

  /* Error */
  ft.generic_magic = js_error_constructor;
  obj = JS_NewCConstructor(ctx, JS_CLASS_ERROR, "Error", ft.generic, 1,
                           LEPUS_CFUNC_constructor_or_func_magic, -1,
                           LEPUS_UNDEFINED, nullptr, 0, js_error_proto_funcs,
                           countof(js_error_proto_funcs), 0);

  for (i = 0; i < JS_NATIVE_ERROR_COUNT; i++) {
    LEPUSValue func_obj;
    const LEPUSCFunctionListEntry *funcs;
    int n_args;
    char buf[ATOM_GET_STR_BUF_SIZE];
    const char *name = nullptr;
    HandleScope block_scope(ctx, &func_obj, HANDLE_TYPE_LEPUS_VALUE);
    if (i != JS_AGGREGATE_ERROR) {
      name = JS_AtomGetStr(ctx, buf, sizeof(buf), JS_ATOM_EvalError + i);
      n_args = 1;
    } else {
      n_args = 2;
      name = "AggregateError";
    }
    funcs = js_native_error_proto_funcs + 2 * i;

    func_obj = JS_NewCConstructor(ctx, -1, name, ft.generic, n_args,
                                  LEPUS_CFUNC_constructor_or_func_magic, i, obj,
                                  NULL, 0, funcs, 2, 0);
    ctx->native_error_proto[i] =
        LEPUS_GetProperty(ctx, func_obj, JS_ATOM_prototype);
  }

  /* Array */
  /* the array prototype is an array */
  JS_NewCConstructor(ctx, JS_CLASS_ARRAY, "Array", js_array_constructor, 1,
                     LEPUS_CFUNC_constructor_or_func, 0, LEPUS_UNDEFINED,
                     js_array_funcs, countof(js_array_funcs),
                     js_array_proto_funcs, countof(js_array_proto_funcs),
                     JS_NEW_CTOR_PROTO_CLASS);

  ctx->array_shape =
      js_new_shape2(ctx, get_proto_obj(ctx->class_proto[JS_CLASS_ARRAY]),
                    JS_PROP_INITIAL_HASH_SIZE, 1);
  add_shape_property(ctx, &ctx->array_shape, NULL, JS_ATOM_length,
                     LEPUS_PROP_WRITABLE | LEPUS_PROP_LENGTH);
}

void JS_AddIntrinsicBaseObjects_GC(LEPUSContext *ctx) {
  int i;
  LEPUSValueConst number_obj;
  LEPUSValue obj1;
  LEPUSCFunctionType ft;

  ctx->throw_type_error = LEPUS_NewCFunction(ctx, js_throw_type_error, NULL, 0);

  /* add caller and arguments properties to throw a TypeError */
  JS_DefineProperty_GC(
      ctx, ctx->function_proto, JS_ATOM_caller, LEPUS_UNDEFINED,
      ctx->throw_type_error, ctx->throw_type_error,
      LEPUS_PROP_HAS_GET | LEPUS_PROP_HAS_SET | LEPUS_PROP_HAS_CONFIGURABLE |
          LEPUS_PROP_CONFIGURABLE);
  JS_DefineProperty_GC(
      ctx, ctx->function_proto, JS_ATOM_arguments, LEPUS_UNDEFINED,
      ctx->throw_type_error, ctx->throw_type_error,
      LEPUS_PROP_HAS_GET | LEPUS_PROP_HAS_SET | LEPUS_PROP_HAS_CONFIGURABLE |
          LEPUS_PROP_CONFIGURABLE);
  js_object_seal(ctx, LEPUS_UNDEFINED, 1,
                 (LEPUSValueConst *)&ctx->throw_type_error, 1);

  /* Object */
  JS_NewCConstructor(ctx, JS_CLASS_OBJECT, "Object", js_object_constructor, 1,
                     LEPUS_CFUNC_constructor_or_func, 0, LEPUS_UNDEFINED,
                     js_object_funcs, countof(js_object_funcs),
                     js_object_proto_funcs, countof(js_object_proto_funcs),
                     JS_NEW_CTOR_PROTO_EXIST);

  /* Function */
  ft.generic_magic = js_function_constructor;
  ctx->function_ctor = JS_NewCConstructor(
      ctx, JS_CLASS_BYTECODE_FUNCTION, "Function", ft.generic, 1,
      LEPUS_CFUNC_constructor_or_func_magic, JS_FUNC_NORMAL, LEPUS_UNDEFINED,
      NULL, 0, js_function_proto_funcs, countof(js_function_proto_funcs),
      JS_NEW_CTOR_PROTO_EXIST);

  /* Iterator prototype */
  ctx->iterator_proto = JS_NewObjectProtoList(
      ctx, ctx->class_proto[JS_CLASS_OBJECT], js_iterator_proto_funcs,
      countof(js_iterator_proto_funcs));

  /* needed to initialize arguments[Symbol.iterator] */
  ctx->array_proto_values = JS_GetPropertyInternal_GC(
      ctx, ctx->class_proto[JS_CLASS_ARRAY], JS_ATOM_values,
      ctx->class_proto[JS_CLASS_ARRAY], 0);

  ctx->class_proto[JS_CLASS_ARRAY_ITERATOR] = JS_NewObjectProtoList(
      ctx, ctx->iterator_proto, js_array_iterator_proto_funcs,
      countof(js_array_iterator_proto_funcs));
  /* parseFloat and parseInteger must be defined before Number
       because of the Number.parseFloat and Number.parseInteger
       aliases */
  LEPUS_SetPropertyFunctionList(ctx, ctx->global_obj, js_global_funcs,
                                countof(js_global_funcs));

  /* Number */
  JS_NewCConstructor(ctx, JS_CLASS_NUMBER, "Number", js_number_constructor, 1,
                     LEPUS_CFUNC_constructor_or_func, 0, LEPUS_UNDEFINED,
                     js_number_funcs, countof(js_number_funcs),
                     js_number_proto_funcs, countof(js_number_proto_funcs),
                     JS_NEW_CTOR_PROTO_CLASS);
  JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_NUMBER],
                   LEPUS_NewInt32(ctx, 0));

  /* Boolean */
  JS_NewCConstructor(ctx, JS_CLASS_BOOLEAN, "Boolean", js_boolean_constructor,
                     1, LEPUS_CFUNC_constructor_or_func, 0, LEPUS_UNDEFINED,
                     nullptr, 0, js_boolean_proto_funcs,
                     countof(js_boolean_proto_funcs), JS_NEW_CTOR_PROTO_CLASS);

  JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_BOOLEAN],
                   LEPUS_NewBool(ctx, FALSE));

  /* String */
  JS_NewCConstructor(ctx, JS_CLASS_STRING, "String", js_string_constructor, 1,
                     LEPUS_CFUNC_constructor_or_func, 0, LEPUS_UNDEFINED,
                     js_string_funcs, countof(js_string_funcs),
                     js_string_proto_funcs, countof(js_string_proto_funcs),
                     JS_NEW_CTOR_PROTO_CLASS);
  JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_STRING],
                   LEPUS_AtomToString(ctx, JS_ATOM_empty_string));

  ctx->class_proto[JS_CLASS_STRING_ITERATOR] = JS_NewObjectProtoList(
      ctx, ctx->iterator_proto, js_string_iterator_proto_funcs,
      countof(js_string_iterator_proto_funcs));

  /* Math: create as autoinit object */
  js_random_init(ctx);
  LEPUS_SetPropertyFunctionList(ctx, ctx->global_obj, js_math_obj,
                                countof(js_math_obj));

  /* ES6 Reflect: create as autoinit object */
  LEPUS_SetPropertyFunctionList(ctx, ctx->global_obj, js_reflect_obj,
                                countof(js_reflect_obj));

  /* ES6 Symbol */
  JS_NewCConstructor(ctx, JS_CLASS_SYMBOL, "Symbol", js_symbol_constructor, 0,
                     LEPUS_CFUNC_constructor_or_func, 0, LEPUS_UNDEFINED,
                     js_symbol_funcs, countof(js_symbol_funcs),
                     js_symbol_proto_funcs, countof(js_symbol_proto_funcs), 0);
  /* ES6 Generator */
  ctx->class_proto[JS_CLASS_GENERATOR] =
      JS_NewObjectProtoList(ctx, ctx->iterator_proto, js_generator_proto_funcs,
                            countof(js_generator_proto_funcs));

  ft.generic_magic = js_function_constructor;
  JS_NewCConstructor(ctx, JS_CLASS_GENERATOR_FUNCTION, "GeneratorFunction",
                     ft.generic, 1, LEPUS_CFUNC_constructor_or_func_magic,
                     JS_FUNC_GENERATOR, ctx->function_ctor, nullptr, 0,
                     js_generator_function_proto_funcs,
                     countof(js_generator_function_proto_funcs),
                     JS_NEW_CTOR_NO_GLOBAL | JS_NEW_CTOR_READONLY);
  JS_SetConstructor2(ctx, ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION],
                     ctx->class_proto[JS_CLASS_GENERATOR],
                     LEPUS_PROP_CONFIGURABLE, LEPUS_PROP_CONFIGURABLE);

  /* global properties */
  ctx->eval_obj = LEPUS_GetProperty(ctx, ctx->global_obj, JS_ATOM_eval);
  JS_DefinePropertyValueStr_GC(ctx, ctx->global_obj, "globalThis",
                               ctx->global_obj,
                               LEPUS_PROP_CONFIGURABLE | LEPUS_PROP_WRITABLE);
  JS_AddIntrinsicBigInt(ctx);
}

static LEPUSValue js_array_buffer_constructor3(
    LEPUSContext *ctx, LEPUSValueConst new_target, uint64_t len,
    LEPUSClassID class_id, uint8_t *buf,
    LEPUSFreeArrayBufferDataFunc *free_func, void *opaque, BOOL alloc_flag) {
  LEPUSValue obj;
  JSArrayBuffer *abuf = NULL;

  obj = js_create_from_ctor_GC(ctx, new_target, class_id);
  if (LEPUS_IsException(obj)) return obj;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  /* XXX: we are currently limited to 2 GB */
  if (len > INT32_MAX) {
    LEPUS_ThrowRangeError(ctx, "invalid array buffer length");
    goto fail;
  }
  abuf = static_cast<JSArrayBuffer *>(
      lepus_malloc(ctx, sizeof(*abuf), ALLOC_TAG_JSArrayBuffer));
  if (!abuf) goto fail;
  func_scope.PushHandle(abuf, HANDLE_TYPE_DIR_HEAP_OBJ);
  abuf->byte_length = len;
  abuf->data = NULL;
  abuf->from_js_heap = false;
  if (alloc_flag) {
    /* the allocation must be done after the object creation */
    abuf->from_js_heap = true;
    abuf->data = static_cast<uint8_t *>(
        lepus_mallocz(ctx, max_int(len, 1), ALLOC_TAG_WITHOUT_PTR));
    if (!abuf->data) goto fail;
  } else {
    abuf->data = buf;
  }
  init_list_head(&abuf->array_list);
  abuf->detached = FALSE;
  abuf->shared = (class_id == JS_CLASS_SHARED_ARRAY_BUFFER);
  abuf->opaque = opaque;
  abuf->free_func = free_func;
  if (alloc_flag && buf) memcpy(abuf->data, buf, len);
  LEPUS_SetOpaque(obj, abuf);
  return obj;
fail:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_array_buffer_constructor2(LEPUSContext *ctx,
                                               LEPUSValueConst new_target,
                                               uint64_t len,
                                               LEPUSClassID class_id) {
  return js_array_buffer_constructor3(ctx, new_target, len, class_id, NULL,
                                      NULL, NULL, TRUE);
}

static LEPUSValue js_array_buffer_constructor1(LEPUSContext *ctx,
                                               LEPUSValueConst new_target,
                                               uint64_t len) {
  return js_array_buffer_constructor2(ctx, new_target, len,
                                      JS_CLASS_ARRAY_BUFFER);
}

// <Primjs begin>

LEPUS_BOOL JS_StrictEq_GC(LEPUSContext *ctx, LEPUSValueConst op1,
                          LEPUSValueConst op2) {
  return js_strict_eq(ctx, op1, op2);
}

LEPUS_BOOL JS_SameValue_GC(LEPUSContext *ctx, LEPUSValueConst op1,
                           LEPUSValueConst op2) {
  return js_same_value(ctx, op1, op2);
}
// <Primjs end>

LEPUSValue JS_NewArrayBuffer_GC(LEPUSContext *ctx, uint8_t *buf, size_t len,
                                LEPUSFreeArrayBufferDataFunc *free_func,
                                void *opaque, BOOL is_shared) {
  return js_array_buffer_constructor3(
      ctx, LEPUS_UNDEFINED, len,
      is_shared ? JS_CLASS_SHARED_ARRAY_BUFFER : JS_CLASS_ARRAY_BUFFER, buf,
      free_func, opaque, FALSE);
}

/* create a new ArrayBuffer of length 'len' and copy 'buf' to it */
LEPUSValue JS_NewArrayBufferCopy_GC(LEPUSContext *ctx, const uint8_t *buf,
                                    size_t len) {
  return js_array_buffer_constructor3(
      ctx, LEPUS_UNDEFINED, len, JS_CLASS_ARRAY_BUFFER,
      const_cast<uint8_t *>(buf), NULL, NULL, TRUE);
}

static LEPUSValue js_array_buffer_constructor(LEPUSContext *ctx,
                                              LEPUSValueConst new_target,
                                              int argc, LEPUSValueConst *argv) {
  uint64_t len;
  if (JS_ToIndex_GC(ctx, &len, argv[0])) return LEPUS_EXCEPTION;
  return js_array_buffer_constructor1(ctx, new_target, len);
}

static LEPUSValue js_shared_array_buffer_constructor(LEPUSContext *ctx,
                                                     LEPUSValueConst new_target,
                                                     int argc,
                                                     LEPUSValueConst *argv) {
  uint64_t len;
  if (JS_ToIndex_GC(ctx, &len, argv[0])) return LEPUS_EXCEPTION;
  return js_array_buffer_constructor2(ctx, new_target, len,
                                      JS_CLASS_SHARED_ARRAY_BUFFER);
}

/* also used for SharedArrayBuffer */
static LEPUSValue js_array_buffer_isView(LEPUSContext *ctx,
                                         LEPUSValueConst this_val, int argc,
                                         LEPUSValueConst *argv) {
  LEPUSObject *p;
  BOOL res;
  res = FALSE;
  if (LEPUS_VALUE_IS_OBJECT(argv[0])) {
    p = LEPUS_VALUE_GET_OBJ(argv[0]);
    if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
        p->class_id <= JS_CLASS_DATAVIEW) {
      res = TRUE;
    }
  }
  return LEPUS_NewBool(ctx, res);
}

static const LEPUSCFunctionListEntry js_array_buffer_funcs[] = {
    LEPUS_CFUNC_DEF("isView", 1, js_array_buffer_isView),
    LEPUS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL),
};

static LEPUSValue JS_ThrowTypeErrorDetachedArrayBuffer(LEPUSContext *ctx) {
  return LEPUS_ThrowTypeError(ctx, "ArrayBuffer is detached");
}

void JS_DetachArrayBuffer_GC(LEPUSContext *ctx, LEPUSValueConst obj) {
  JSArrayBuffer *abuf =
      static_cast<JSArrayBuffer *>(LEPUS_GetOpaque(obj, JS_CLASS_ARRAY_BUFFER));
  struct list_head *el;

  if (!abuf || abuf->detached) return;
  if (!abuf->from_js_heap && abuf->free_func)
    abuf->free_func(ctx->rt, abuf->opaque, abuf->data);
  abuf->data = NULL;
  abuf->byte_length = 0;
  abuf->detached = TRUE;

  list_for_each(el, &abuf->array_list) {
    JSTypedArray *ta;
    LEPUSObject *p;

    ta = list_entry(el, JSTypedArray, link);
    p = ta->obj;
    /* Note: the typed array length and offset fields are not modified */
    if (p->class_id != JS_CLASS_DATAVIEW) {
      p->u.array.count = 0;
      p->u.array.u.ptr = NULL;
    }
  }
}

/* return NULL if exception. WARNING: any LEPUS call can detach the
   buffer and render the returned pointer invalid */
uint8_t *JS_GetArrayBuffer_GC(LEPUSContext *ctx, size_t *psize,
                              LEPUSValueConst obj) {
  JSArrayBuffer *abuf = js_get_array_buffer(ctx, obj);
  if (!abuf) goto fail;
  if (abuf->detached) {
    JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
    goto fail;
  }
  *psize = abuf->byte_length;
  return abuf->data;
fail:
  *psize = 0;
  return NULL;
}

uint8_t *JS_MoveArrayBuffer_GC(LEPUSContext *ctx, size_t *psize,
                               LEPUSValueConst obj) {
  auto *abuf = js_get_array_buffer(ctx, obj);
  void *ret = nullptr;
  if (!abuf) goto fail;
  if (abuf->detached) {
    JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
    goto fail;
  }
  *psize = abuf->byte_length;
  ret = abuf->data;
  abuf->data = nullptr;
  abuf->byte_length = 0;
  JS_DetachArrayBuffer_GC(ctx, obj);
  return static_cast<uint8_t *>(ret);
fail:
  *psize = 0;
  return nullptr;
}

static LEPUSValue js_array_buffer_slice(LEPUSContext *ctx,
                                        LEPUSValueConst this_val, int argc,
                                        LEPUSValueConst *argv, int class_id) {
  JSArrayBuffer *abuf, *new_abuf;
  int64_t len, start, end, new_len;
  LEPUSValue ctor, new_obj;

  abuf =
      static_cast<JSArrayBuffer *>(LEPUS_GetOpaque2(ctx, this_val, class_id));
  if (!abuf) return LEPUS_EXCEPTION;
  if (abuf->detached) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
  len = abuf->byte_length;

  if (JS_ToInt64Clamp(ctx, &start, argv[0], 0, len, len))
    return LEPUS_EXCEPTION;

  end = len;
  if (!LEPUS_IsUndefined(argv[1])) {
    if (JS_ToInt64Clamp(ctx, &end, argv[1], 0, len, len))
      return LEPUS_EXCEPTION;
  }
  new_len = max_int64(end - start, 0);
  ctor = JS_SpeciesConstructor(ctx, this_val, LEPUS_UNDEFINED);
  if (LEPUS_IsException(ctor)) return ctor;
  HandleScope func_scope(ctx, &ctor, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_IsUndefined(ctor)) {
    new_obj =
        js_array_buffer_constructor2(ctx, LEPUS_UNDEFINED, new_len, class_id);
  } else {
    HandleScope block_scope(ctx->rt);
    LEPUSValue args[1];
    args[0] = JS_NewInt64_GC(ctx, new_len);
    block_scope.PushHandle(&args[0], HANDLE_TYPE_LEPUS_VALUE);
    new_obj = JS_CallConstructor_GC(ctx, ctor, 1,
                                    reinterpret_cast<LEPUSValueConst *>(args));
  }
  if (LEPUS_IsException(new_obj)) return new_obj;
  func_scope.PushHandle(&new_obj, HANDLE_TYPE_LEPUS_VALUE);
  new_abuf =
      static_cast<JSArrayBuffer *>(LEPUS_GetOpaque2(ctx, new_obj, class_id));
  if (!new_abuf) goto fail;
  if (js_same_value(ctx, new_obj, this_val)) {
    LEPUS_ThrowTypeError(ctx, "cannot use identical ArrayBuffer");
    goto fail;
  }
  if (new_abuf->detached) {
    JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
    goto fail;
  }
  if (new_abuf->byte_length < new_len) {
    LEPUS_ThrowTypeError(ctx, "new ArrayBuffer is too small");
    goto fail;
  }
  /* must test again because of side effects */
  if (abuf->detached) {
    JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
    goto fail;
  }
  memcpy(new_abuf->data, abuf->data + start, new_len);
  return new_obj;
fail:
  return LEPUS_EXCEPTION;
}

static const LEPUSCFunctionListEntry js_array_buffer_proto_funcs[] = {
    LEPUS_CGETSET_MAGIC_DEF("byteLength", js_array_buffer_get_byteLength, NULL,
                            JS_CLASS_ARRAY_BUFFER),
    LEPUS_CFUNC_MAGIC_DEF("slice", 2, js_array_buffer_slice,
                          JS_CLASS_ARRAY_BUFFER),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "ArrayBuffer",
                          LEPUS_PROP_CONFIGURABLE),
};

/* SharedArrayBuffer */

static const LEPUSCFunctionListEntry js_shared_array_buffer_funcs[] = {
    LEPUS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL),
};

static const LEPUSCFunctionListEntry js_shared_array_buffer_proto_funcs[] = {
    LEPUS_CGETSET_MAGIC_DEF("byteLength", js_array_buffer_get_byteLength, NULL,
                            JS_CLASS_SHARED_ARRAY_BUFFER),
    LEPUS_CFUNC_MAGIC_DEF("slice", 2, js_array_buffer_slice,
                          JS_CLASS_SHARED_ARRAY_BUFFER),
    LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "SharedArrayBuffer",
                          LEPUS_PROP_CONFIGURABLE),
};

/* WARNING: 'p' must be a typed array. Works even if the array buffer
   is detached */
static uint32_t typed_array_get_length(LEPUSContext *ctx, LEPUSObject *p) {
  JSTypedArray *ta = p->u.typed_array;
  int size_log2 = typed_array_size_log2(p->class_id);
  return ta->length >> size_log2;
}

static int validate_typed_array(LEPUSContext *ctx, LEPUSValueConst this_val) {
  LEPUSObject *p;
  p = get_typed_array(ctx, this_val, 0);
  if (!p) return -1;
  if (typed_array_is_detached(ctx, p)) {
    JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
    return -1;
  }
  return 0;
}

static LEPUSValue js_typed_array_get_toStringTag(LEPUSContext *ctx,
                                                 LEPUSValueConst this_val) {
  LEPUSObject *p;
  if (LEPUS_VALUE_IS_NOT_OBJECT(this_val)) return LEPUS_UNDEFINED;
  p = LEPUS_VALUE_GET_OBJ(this_val);
  if (!(p->class_id >= JS_CLASS_UINT8C_ARRAY &&
        p->class_id <= JS_CLASS_BIG_UINT64_ARRAY))
    return LEPUS_UNDEFINED;
  return JS_AtomToString_GC(ctx, ctx->rt->class_array[p->class_id].class_name);
}

static LEPUSValue js_typed_array_set_internal(LEPUSContext *ctx,
                                              LEPUSValueConst dst,
                                              LEPUSValueConst src,
                                              LEPUSValueConst off) {
  LEPUSObject *p;
  LEPUSObject *src_p;
  uint32_t i;
  int64_t src_len, offset;
  LEPUSValue val = LEPUS_UNDEFINED, src_obj = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);

  p = get_typed_array(ctx, dst, 0);
  if (!p) goto fail;
  if (JS_ToInt64Sat(ctx, &offset, off)) goto fail;
  if (offset < 0) goto range_error;
  if (typed_array_is_detached(ctx, p)) {
  detached:
    JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
    goto fail;
  }
  src_obj = JS_ToObject_GC(ctx, src);
  if (LEPUS_IsException(src_obj)) goto fail;
  func_scope.PushHandle(&src_obj, HANDLE_TYPE_LEPUS_VALUE);
  src_p = LEPUS_VALUE_GET_OBJ(src_obj);
  if (src_p->class_id >= JS_CLASS_UINT8C_ARRAY &&
      src_p->class_id <= JS_CLASS_BIG_UINT64_ARRAY) {
    JSTypedArray *dest_ta = p->u.typed_array;
    JSArrayBuffer *dest_abuf = dest_ta->buffer->u.array_buffer;
    JSTypedArray *src_ta = src_p->u.typed_array;
    JSArrayBuffer *src_abuf = src_ta->buffer->u.array_buffer;
    int shift = typed_array_size_log2(p->class_id);

    if (src_abuf->detached) goto detached;

    src_len = src_p->u.array.count;
    if (offset > (int64_t)(p->u.array.count - src_len)) goto range_error;

    /* copying between typed objects */
    if (src_p->class_id == p->class_id) {
      /* same type, use memmove */
      memmove(dest_abuf->data + dest_ta->offset + (offset << shift),
              src_abuf->data + src_ta->offset, src_len << shift);
      goto done;
    }
    if (dest_abuf->data == src_abuf->data) {
      /* copying between the same buffer using different types of mappings
         would require a temporary buffer */
    }
    /* otherwise, default behavior is slow but correct */
  } else {
    if (js_get_length64(ctx, &src_len, src_obj)) goto fail;
    if (offset > (int64_t)(p->u.array.count - src_len)) {
    range_error:
      LEPUS_ThrowRangeError(ctx, "invalid array length");
      goto fail;
    }
  }
  for (i = 0; i < src_len; i++) {
    val = JS_GetPropertyUint32_GC(ctx, src_obj, i);
    if (LEPUS_IsException(val)) goto fail;
    if (JS_SetPropertyUint32_GC(ctx, dst, offset + i, val) < 0) goto fail;
  }
done:
  return LEPUS_UNDEFINED;
fail:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_typed_array_set(LEPUSContext *ctx,
                                     LEPUSValueConst this_val, int argc,
                                     LEPUSValueConst *argv) {
  LEPUSValueConst offset = LEPUS_UNDEFINED;
  if (argc > 1) {
    offset = argv[1];
  }
  HandleScope func_scope(ctx, &offset, HANDLE_TYPE_LEPUS_VALUE);
  return js_typed_array_set_internal(ctx, this_val, argv[0], offset);
}

static LEPUSValue js_create_typed_array_iterator(LEPUSContext *ctx,
                                                 LEPUSValueConst this_val,
                                                 int argc,
                                                 LEPUSValueConst *argv,
                                                 int magic) {
  if (validate_typed_array(ctx, this_val)) return LEPUS_EXCEPTION;
  return js_create_array_iterator(ctx, this_val, argc, argv, magic);
}

/* return < 0 if exception */
static int js_typed_array_get_length_internal(LEPUSContext *ctx,
                                              LEPUSValueConst obj) {
  LEPUSObject *p;
  p = get_typed_array(ctx, obj, 0);
  if (!p) return -1;
  if (typed_array_is_detached(ctx, p)) {
    JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
    return -1;
  }
  return p->u.array.count;
}

static LEPUSValue js_typed_array_create(LEPUSContext *ctx, LEPUSValueConst ctor,
                                        int argc, LEPUSValueConst *argv) {
  LEPUSValue ret;
  int new_len;
  int64_t len;

  ret = JS_CallConstructor_GC(ctx, ctor, argc, argv);
  if (LEPUS_IsException(ret)) return ret;
  HandleScope func_scope(ctx, &ret, HANDLE_TYPE_LEPUS_VALUE);
  /* validate the typed array */
  new_len = js_typed_array_get_length_internal(ctx, ret);
  if (new_len < 0) goto fail;
  if (argc == 1) {
    /* ensure that it is large enough */
    if (JS_ToLengthFree(ctx, &len, argv[0])) goto fail;
    if (new_len < len) {
      LEPUS_ThrowTypeError(ctx, "TypedArray length is too small");
    fail:
      return LEPUS_EXCEPTION;
    }
  }
  return ret;
}

#if 0
static LEPUSValue js_typed_array___create(LEPUSContext *ctx,
                                       LEPUSValueConst this_val,
                                       int argc, LEPUSValueConst *argv) {
    return js_typed_array_create(ctx, argv[0], max_int(argc - 1, 0), argv + 1);
}
#endif

QJS_STATIC LEPUSValue js_typed_array___speciesCreate(LEPUSContext *ctx,
                                                     LEPUSValueConst this_val,
                                                     int argc,
                                                     LEPUSValueConst *argv) {
  LEPUSValueConst obj;
  LEPUSObject *p;
  LEPUSValue ctor, ret;
  int argc1;

  obj = argv[0];
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  p = get_typed_array(ctx, obj, 0);
  if (!p) return LEPUS_EXCEPTION;
  ctor = JS_SpeciesConstructor(ctx, obj, LEPUS_UNDEFINED);
  if (LEPUS_IsException(ctor)) return ctor;
  func_scope.PushHandle(&ctor, HANDLE_TYPE_LEPUS_VALUE);
  argc1 = max_int(argc - 1, 0);
  if (LEPUS_IsUndefined(ctor)) {
    ret = js_typed_array_constructor_GC(ctx, LEPUS_UNDEFINED, argc1, argv + 1,
                                        p->class_id);
  } else {
    ret = js_typed_array_create(ctx, ctor, argc1, argv + 1);
  }
  return ret;
}

static LEPUSValue js_typed_array_from(LEPUSContext *ctx,
                                      LEPUSValueConst this_val, int argc,
                                      LEPUSValueConst *argv) {
  // from(items, mapfn = void 0, this_arg = void 0)
  LEPUSValueConst items = argv[0], mapfn, this_arg;
  HandleScope func_scope(ctx, &items, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSValueConst args[2];
  func_scope.PushLEPUSValueArrayHandle(args, 2);
  LEPUSValue stack[2];
  LEPUSValue iter = LEPUS_UNDEFINED, arr = LEPUS_UNDEFINED;
  LEPUSValue r = LEPUS_UNDEFINED, v = LEPUS_UNDEFINED, v2 = LEPUS_UNDEFINED;
  func_scope.PushHandle(&iter, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&arr, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&r, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&v, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&v2, HANDLE_TYPE_LEPUS_VALUE);
  int64_t k, len;
  int done, mapping;

  mapping = FALSE;
  mapfn = LEPUS_UNDEFINED;
  this_arg = LEPUS_UNDEFINED;
  func_scope.PushHandle(&mapfn, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&this_arg, HANDLE_TYPE_LEPUS_VALUE);
  r = LEPUS_UNDEFINED;
  arr = LEPUS_UNDEFINED;
  func_scope.PushLEPUSValueArrayHandle(stack, 2);

  if (argc > 1) {
    mapfn = argv[1];
    if (!LEPUS_IsUndefined(mapfn)) {
      if (check_function(ctx, mapfn)) goto exception;
      mapping = 1;
      if (argc > 2) this_arg = argv[2];
    }
  }
  iter =
      JS_GetPropertyInternal_GC(ctx, items, JS_ATOM_Symbol_iterator, items, 0);
  if (LEPUS_IsException(iter)) goto exception;
  if (!LEPUS_IsUndefined(iter)) {
    arr = JS_NewArray_GC(ctx);
    if (LEPUS_IsException(arr)) goto exception;
    stack[0] = items;
    if (js_for_of_start_gc(ctx, &stack[1], FALSE)) goto exception;
    for (k = 0;; k++) {
      v = JS_IteratorNext(ctx, stack[0], stack[1], 0, NULL, &done);
      if (LEPUS_IsException(v)) goto exception_close;
      if (done) break;
      if (JS_DefinePropertyValueInt64_GC(
              ctx, arr, k, v, LEPUS_PROP_C_W_E | LEPUS_PROP_THROW) < 0)
        goto exception_close;
    }
  } else {
    arr = JS_ToObject_GC(ctx, items);
    if (LEPUS_IsException(arr)) goto exception;
  }
  if (js_get_length64(ctx, &len, arr) < 0) goto exception;
  v = JS_NewInt64_GC(ctx, len);
  args[0] = v;
  r = js_typed_array_create(ctx, this_val, 1, args);
  if (LEPUS_IsException(r)) goto exception;
  for (k = 0; k < len; k++) {
    v = JS_GetPropertyInt64(ctx, arr, k);
    if (LEPUS_IsException(v)) goto exception;
    if (mapping) {
      args[0] = v;
      args[1] = LEPUS_NewInt32(ctx, k);
      v2 = JS_Call_GC(ctx, mapfn, this_arg, 2, args);
      v = v2;
      if (LEPUS_IsException(v)) goto exception;
    }
    if (JS_SetPropertyInt64_GC(ctx, r, k, v) < 0) goto exception;
  }
  goto done;

exception_close:
  if (!LEPUS_IsUndefined(stack[0])) JS_IteratorClose(ctx, stack[0], TRUE);
exception:
  r = LEPUS_EXCEPTION;
done:
  return r;
}

static LEPUSValue js_typed_array_of(LEPUSContext *ctx, LEPUSValueConst this_val,
                                    int argc, LEPUSValueConst *argv) {
  LEPUSValue obj;
  LEPUSValueConst args[1];
  int i;

  args[0] = LEPUS_NewInt32(ctx, argc);
  HandleScope func_scope(ctx, &args[0], HANDLE_TYPE_LEPUS_VALUE);
  obj = js_typed_array_create(ctx, this_val, 1, args);
  if (LEPUS_IsException(obj)) return obj;
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);

  for (i = 0; i < argc; i++) {
    if (JS_SetPropertyUint32_GC(ctx, obj, i, argv[i]) < 0) {
      return LEPUS_EXCEPTION;
    }
  }
  return obj;
}

static LEPUSValue js_typed_array_copyWithin(LEPUSContext *ctx,
                                            LEPUSValueConst this_val, int argc,
                                            LEPUSValueConst *argv) {
  LEPUSObject *p;
  int len, to, from, final, count, shift;

  len = js_typed_array_get_length_internal(ctx, this_val);
  if (len < 0) return LEPUS_EXCEPTION;

  if (JS_ToInt32Clamp(ctx, &to, argv[0], 0, len, len)) return LEPUS_EXCEPTION;

  if (JS_ToInt32Clamp(ctx, &from, argv[1], 0, len, len)) return LEPUS_EXCEPTION;

  final = len;
  if (argc > 2 && !LEPUS_IsUndefined(argv[2])) {
    if (JS_ToInt32Clamp(ctx, &final, argv[2], 0, len, len))
      return LEPUS_EXCEPTION;
  }

  count = min_int(final - from, len - to);
  if (count > 0) {
    p = LEPUS_VALUE_GET_OBJ(this_val);
    if (typed_array_is_detached(ctx, p))
      return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
    shift = typed_array_size_log2(p->class_id);
    memmove(p->u.array.u.uint8_ptr + (to << shift),
            p->u.array.u.uint8_ptr + (from << shift), count << shift);
  }
  return this_val;
}

static LEPUSValue js_typed_array_fill(LEPUSContext *ctx,
                                      LEPUSValueConst this_val, int argc,
                                      LEPUSValueConst *argv) {
  LEPUSObject *p;
  int len, k, final, shift;
  uint64_t v64;

  len = js_typed_array_get_length_internal(ctx, this_val);
  if (len < 0) return LEPUS_EXCEPTION;
  p = LEPUS_VALUE_GET_OBJ(this_val);

  if (p->class_id == JS_CLASS_UINT8C_ARRAY) {
    int32_t v;
    if (JS_ToUint8ClampFree(ctx, &v, argv[0])) return LEPUS_EXCEPTION;
    v64 = v;
  } else if (p->class_id <= JS_CLASS_UINT32_ARRAY) {
    uint32_t v;
    if (JS_ToInt32_GC(ctx, reinterpret_cast<int32_t *>(&v), argv[0]))
      return LEPUS_EXCEPTION;
    v64 = v;
  } else if (p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
    double d;
    if (JS_ToFloat64_GC(ctx, &d, argv[0])) return LEPUS_EXCEPTION;
    if (p->class_id == JS_CLASS_FLOAT32_ARRAY) {
      union {
        float f;
        uint32_t u32;
      } u;
      u.f = d;
      v64 = u.u32;
    } else {
      JSFloat64Union u;
      u.d = d;
      v64 = u.u64;
    }
  } else {
    if (LEPUS_ToBigInt64(ctx, (int64_t *)&v64, argv[0])) return LEPUS_EXCEPTION;
  }

  k = 0;
  if (argc > 1) {
    if (JS_ToInt32Clamp(ctx, &k, argv[1], 0, len, len)) return LEPUS_EXCEPTION;
  }

  final = len;
  if (argc > 2 && !LEPUS_IsUndefined(argv[2])) {
    if (JS_ToInt32Clamp(ctx, &final, argv[2], 0, len, len))
      return LEPUS_EXCEPTION;
  }

  if (typed_array_is_detached(ctx, p))
    return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
  shift = typed_array_size_log2(p->class_id);
  switch (shift) {
    case 0:
      if (k < final) {
        memset(p->u.array.u.uint8_ptr + k, v64, final - k);
      }
      break;
    case 1:
      for (; k < final; k++) {
        p->u.array.u.uint16_ptr[k] = v64;
      }
      break;
    case 2:
      for (; k < final; k++) {
        p->u.array.u.uint32_ptr[k] = v64;
      }
      break;
    case 3:
      for (; k < final; k++) {
        p->u.array.u.uint64_ptr[k] = v64;
      }
      break;
    default:
      abort();
  }
  return this_val;
}

static LEPUSValue js_typed_array_find(LEPUSContext *ctx,
                                      LEPUSValueConst this_val, int argc,
                                      LEPUSValueConst *argv, int mode) {
  HandleScope func_scope(ctx);
  LEPUSValueConst func, this_arg;
  LEPUSValueConst args[3];
  func_scope.PushLEPUSValueArrayHandle(args, 3);
  LEPUSValue val, index_val = LEPUS_UNDEFINED, res = LEPUS_UNDEFINED;
  func_scope.PushHandle(&index_val, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&res, HANDLE_TYPE_LEPUS_VALUE);
  int len, k, end, dir;
  bool find_index = mode == ArrayFindIndex || mode == ArrayFindLastIndex;

  val = LEPUS_UNDEFINED;
  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  len = js_typed_array_get_length_internal(ctx, this_val);
  if (len < 0) goto exception;

  func = argv[0];
  func_scope.PushHandle(&func, HANDLE_TYPE_LEPUS_VALUE);
  if (check_function(ctx, func)) goto exception;

  this_arg = LEPUS_UNDEFINED;
  func_scope.PushHandle(&this_arg, HANDLE_TYPE_LEPUS_VALUE);
  if (argc > 1) this_arg = argv[1];
  k = 0;
  end = len;
  dir = 1;

  if (mode == ArrayFindLast || mode == ArrayFindLastIndex) {
    k = len - 1;
    dir = -1;
    end = -1;
  }

  for (; k != end; k += dir) {
    index_val = LEPUS_NewInt32(ctx, k);
    val = JS_GetPropertyValue_GC(ctx, this_val, index_val);
    if (LEPUS_IsException(val)) goto exception;
    args[0] = val;
    args[1] = index_val;
    args[2] = this_val;
    res = JS_Call_GC(ctx, func, this_arg, 3, args);
    if (LEPUS_IsException(res)) goto exception;
    if (JS_ToBoolFree_GC(ctx, res)) {
      if (find_index) {
        return index_val;
      } else {
        return val;
      }
    }
  }
  if (find_index)
    return LEPUS_NewInt32(ctx, -1);
  else
    return LEPUS_UNDEFINED;

exception:
  return LEPUS_EXCEPTION;
}

#define special_indexOf 0
#define special_lastIndexOf 1
#define special_includes -1

static LEPUSValue js_typed_array_indexOf(LEPUSContext *ctx,
                                         LEPUSValueConst this_val, int argc,
                                         LEPUSValueConst *argv, int special) {
  LEPUSObject *p;
  int len, is_int, is_big, k, stop, inc, res = -1;
  int64_t tag;
  int64_t v64;
  double d;
  float f;

  len = js_typed_array_get_length_internal(ctx, this_val);
  if (len < 0) goto exception;
  if (len == 0) goto done;

  if (special == special_lastIndexOf) {
    k = len - 1;
    if (argc > 1) {
      if (LEPUS_ToFloat64(ctx, &d, argv[1])) goto exception;
      // need convert (double)d to int.
      d = DoubleToInteger(d);
      if (d >= 0) {
        if (d < k) {
          k = d;
        }
      } else {
        d += len;
        if (d < 0) goto done;
        k = d;
      }
    }
    stop = -1;
    inc = -1;
  } else {
    k = 0;
    if (argc > 1) {
      if (JS_ToInt32Clamp(ctx, &k, argv[1], 0, len, len)) goto exception;
    }
    stop = len;
    inc = 1;
  }

  p = LEPUS_VALUE_GET_OBJ(this_val);
  /* if the array was detached, no need to go further (but no
     exception is raised) */
  if (typed_array_is_detached(ctx, p)) {
    /* "includes" scans all the properties, so "undefined" can match */
    if (special == special_includes && LEPUS_IsUndefined(argv[0]) && len > 0)
      res = 0;
    goto done;
  }

  is_big = 0;
  is_int = 0; /* avoid warning */
  v64 = 0;    /* avoid warning */
  tag = LEPUS_VALUE_GET_NORM_TAG(argv[0]);
  if (tag == LEPUS_TAG_INT) {
    is_int = 1;
    v64 = LEPUS_VALUE_GET_INT(argv[0]);
    d = v64;
  } else if (tag == LEPUS_TAG_FLOAT64) {
    d = LEPUS_VALUE_GET_FLOAT64(argv[0]);
    v64 = d;
    is_int = (v64 == d);
  } else if (tag == LEPUS_TAG_BIG_INT) {
    is_big = 1;
  } else {
    goto done;
  }

  p = LEPUS_VALUE_GET_OBJ(this_val);
  switch (p->class_id) {
    case JS_CLASS_INT8_ARRAY:
      if (is_int && (int8_t)v64 == v64) goto scan8;
      break;
    case JS_CLASS_UINT8C_ARRAY:
    case JS_CLASS_UINT8_ARRAY:
      if (is_int && (uint8_t)v64 == v64) {
        const uint8_t *pv, *pp;
        uint16_t v;
      scan8:
        pv = p->u.array.u.uint8_ptr;
        v = v64;
        if (inc > 0) {
          pp = static_cast<const uint8_t *>(memchr(pv + k, v, len - k));
          if (pp) res = pp - pv;
        } else {
          for (; k != stop; k += inc) {
            if (pv[k] == v) {
              res = k;
              break;
            }
          }
        }
      }
      break;
    case JS_CLASS_INT16_ARRAY:
      if (is_int && (int16_t)v64 == v64) goto scan16;
      break;
    case JS_CLASS_UINT16_ARRAY:
      if (is_int && (uint16_t)v64 == v64) {
        const uint16_t *pv;
        uint16_t v;
      scan16:
        pv = p->u.array.u.uint16_ptr;
        v = v64;
        for (; k != stop; k += inc) {
          if (pv[k] == v) {
            res = k;
            break;
          }
        }
      }
      break;
    case JS_CLASS_INT32_ARRAY:
      if (is_int && (int32_t)v64 == v64) goto scan32;
      break;
    case JS_CLASS_UINT32_ARRAY:
      if (is_int && (uint32_t)v64 == v64) {
        const uint32_t *pv;
        uint32_t v;
      scan32:
        pv = p->u.array.u.uint32_ptr;
        v = v64;
        for (; k != stop; k += inc) {
          if (pv[k] == v) {
            res = k;
            break;
          }
        }
      }
      break;
    case JS_CLASS_FLOAT32_ARRAY:
      if (is_big) break;
      if (isnan(d)) {
        const float *pv = p->u.array.u.float_ptr;
        /* special case: indexOf returns -1, includes finds NaN */
        if (special != special_includes) goto done;
        for (; k != stop; k += inc) {
          if (isnan(pv[k])) {
            res = k;
            break;
          }
        }
      } else if ((f = static_cast<float>(d)) == d) {
        const float *pv = p->u.array.u.float_ptr;
        for (; k != stop; k += inc) {
          if (pv[k] == f) {
            res = k;
            break;
          }
        }
      }
      break;
    case JS_CLASS_FLOAT64_ARRAY:
      if (is_big) break;
      if (isnan(d)) {
        const double *pv = p->u.array.u.double_ptr;
        /* special case: indexOf returns -1, includes finds NaN */
        if (special != special_includes) goto done;
        for (; k != stop; k += inc) {
          if (isnan(pv[k])) {
            res = k;
            break;
          }
        }
      } else {
        const double *pv = p->u.array.u.double_ptr;
        for (; k != stop; k += inc) {
          if (pv[k] == d) {
            res = k;
            break;
          }
        }
      }
      break;
    case JS_CLASS_BIG_INT64_ARRAY:
    case JS_CLASS_BIG_UINT64_ARRAY:
      if (is_big || is_strict_mode(ctx)) {
        /* generic loop for bignums, argv[0] is a bignum != NaN */
        /* XXX: optimize with explicit values */
        for (; k != stop; k += inc) {
          HandleScope block_scope(ctx->rt);
          LEPUSValue v = JS_GetPropertyUint32_GC(ctx, this_val, k);
          block_scope.PushHandle(&v, HANDLE_TYPE_LEPUS_VALUE);
          int ret;
          if (LEPUS_IsException(v)) goto exception;
          ret = js_same_value_zero(ctx, v, argv[0]);
          if (ret) {
            if (ret < 0) goto exception;
            res = k;
            break;
          }
        }
      }
      break;
  }

done:
  if (special == special_includes)
    return LEPUS_NewBool(ctx, res >= 0);
  else
    return LEPUS_NewInt32(ctx, res);

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_typed_array_join(LEPUSContext *ctx,
                                      LEPUSValueConst this_val, int argc,
                                      LEPUSValueConst *argv,
                                      int toLocaleString) {
  LEPUSValue sep = LEPUS_UNDEFINED, el = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &sep, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&el, HANDLE_TYPE_LEPUS_VALUE);
  StringBuffer b_s, *b = &b_s;
  JSString *p = NULL;
  int i, n;
  int c;

  n = js_typed_array_get_length_internal(ctx, this_val);
  if (n < 0) goto exception;

  c = ','; /* default separator */
  if (!toLocaleString && argc > 0 && !LEPUS_IsUndefined(argv[0])) {
    sep = JS_ToString_GC(ctx, argv[0]);
    if (LEPUS_IsException(sep)) goto exception;
    p = LEPUS_VALUE_GET_STRING(sep);
    if (p->len == 1 && !p->is_wide_char)
      c = p->u.str8[0];
    else
      c = -1;
  }
  string_buffer_init(ctx, b, 0);
  func_scope.PushHandle(&b->str, HANDLE_TYPE_HEAP_OBJ);

  /* XXX: optimize with direct access */
  for (i = 0; i < n; i++) {
    if (i > 0) {
      if (c >= 0) {
        if (string_buffer_putc8(b, c)) goto fail;
      } else {
        if (string_buffer_concat(b, p, 0, p->len)) goto fail;
      }
    }
    el = JS_GetPropertyUint32_GC(ctx, this_val, i);
    /* Can return undefined for example if the typed array is detached */
    if (!LEPUS_IsNull(el) && !LEPUS_IsUndefined(el)) {
      if (LEPUS_IsException(el)) goto fail;
      if (toLocaleString) {
        el = JS_ToLocaleStringFree(ctx, el);
      }
      if (string_buffer_concat_value_free(b, el)) goto fail;
    }
  }
  return string_buffer_end(b);

fail:
  b->str = NULL;
exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_typed_array_reverse(LEPUSContext *ctx,
                                         LEPUSValueConst this_val, int argc,
                                         LEPUSValueConst *argv) {
  LEPUSObject *p;
  int len;

  len = js_typed_array_get_length_internal(ctx, this_val);
  if (len < 0) return LEPUS_EXCEPTION;
  if (len > 0) {
    p = LEPUS_VALUE_GET_OBJ(this_val);
    switch (typed_array_size_log2(p->class_id)) {
      case 0: {
        uint8_t *p1 = p->u.array.u.uint8_ptr;
        uint8_t *p2 = p1 + len - 1;
        while (p1 < p2) {
          uint8_t v = *p1;
          *p1++ = *p2;
          *p2-- = v;
        }
      } break;
      case 1: {
        uint16_t *p1 = p->u.array.u.uint16_ptr;
        uint16_t *p2 = p1 + len - 1;
        while (p1 < p2) {
          uint16_t v = *p1;
          *p1++ = *p2;
          *p2-- = v;
        }
      } break;
      case 2: {
        uint32_t *p1 = p->u.array.u.uint32_ptr;
        uint32_t *p2 = p1 + len - 1;
        while (p1 < p2) {
          uint32_t v = *p1;
          *p1++ = *p2;
          *p2-- = v;
        }
      } break;
      case 3: {
        uint64_t *p1 = p->u.array.u.uint64_ptr;
        uint64_t *p2 = p1 + len - 1;
        while (p1 < p2) {
          uint64_t v = *p1;
          *p1++ = *p2;
          *p2-- = v;
        }
      } break;
      default:
        abort();
    }
  }
  return this_val;
}

static LEPUSValue js_typed_array_slice(LEPUSContext *ctx,
                                       LEPUSValueConst this_val, int argc,
                                       LEPUSValueConst *argv) {
  HandleScope func_scope(ctx);
  LEPUSValueConst args[2];
  func_scope.PushLEPUSValueArrayHandle(args, 2);
  LEPUSValue arr, val = LEPUS_UNDEFINED;
  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  LEPUSObject *p, *p1;
  int n, len, start, final, count, shift;

  arr = LEPUS_UNDEFINED;
  func_scope.PushHandle(&arr, HANDLE_TYPE_LEPUS_VALUE);
  len = js_typed_array_get_length_internal(ctx, this_val);
  if (len < 0) goto exception;

  if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) goto exception;
  final = len;
  if (!LEPUS_IsUndefined(argv[1])) {
    if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len)) goto exception;
  }
  count = max_int(final - start, 0);

  p = get_typed_array(ctx, this_val, 0);
  if (p == NULL) goto exception;
  shift = typed_array_size_log2(p->class_id);

  args[0] = this_val;
  args[1] = LEPUS_NewInt32(ctx, count);
  arr = js_typed_array___speciesCreate(ctx, LEPUS_UNDEFINED, 2, args);
  if (LEPUS_IsException(arr)) goto exception;

  if (count > 0) {
    if (validate_typed_array(ctx, this_val) || validate_typed_array(ctx, arr))
      goto exception;

    p1 = get_typed_array(ctx, arr, 0);
    if (p1 != NULL && p->class_id == p1->class_id &&
        typed_array_get_length(ctx, p1) >= count &&
        typed_array_get_length(ctx, p) >= start + count) {
      memmove(p1->u.array.u.uint8_ptr,
              p->u.array.u.uint8_ptr + (start << shift), count << shift);
    } else {
      for (n = 0; n < count; n++) {
        val = JS_GetPropertyValue_GC(ctx, this_val,
                                     LEPUS_NewInt32(ctx, start + n));
        if (LEPUS_IsException(val)) goto exception;
        if (JS_SetPropertyValue_GC(ctx, arr, LEPUS_NewInt32(ctx, n), val,
                                   LEPUS_PROP_THROW) < 0)
          goto exception;
      }
    }
  }
  return arr;

exception:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_typed_array_subarray(LEPUSContext *ctx,
                                          LEPUSValueConst this_val, int argc,
                                          LEPUSValueConst *argv) {
  HandleScope func_scope(ctx);
  LEPUSValueConst args[4];
  func_scope.PushLEPUSValueArrayHandle(args, 4);
  LEPUSValue arr, byteOffset, ta_buffer;
  LEPUSObject *p;
  int len, start, final, count, shift, offset;

  p = get_typed_array(ctx, this_val, 0);
  if (!p) goto exception;
  len = p->u.array.count;
  if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) goto exception;

  final = len;
  if (!LEPUS_IsUndefined(argv[1])) {
    if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len)) goto exception;
  }
  count = max_int(final - start, 0);
  byteOffset = js_typed_array_get_byteOffset(ctx, this_val, 0);
  if (LEPUS_IsException(byteOffset)) goto exception;
  func_scope.PushHandle(&byteOffset, HANDLE_TYPE_LEPUS_VALUE);
  shift = typed_array_size_log2(p->class_id);
  offset = LEPUS_VALUE_GET_INT(byteOffset) + (start << shift);
  ta_buffer = js_typed_array_get_buffer(ctx, this_val, 0);
  if (LEPUS_IsException(ta_buffer)) goto exception;
  args[0] = this_val;
  args[1] = ta_buffer;
  args[2] = LEPUS_NewInt32(ctx, offset);
  args[3] = LEPUS_NewInt32(ctx, count);
  arr = js_typed_array___speciesCreate(ctx, LEPUS_UNDEFINED, 4, args);
  return arr;

exception:
  return LEPUS_EXCEPTION;
}

/* TypedArray.prototype.sort */

static int js_cmp_doubles(double x, double y) {
  if (isnan(x)) return isnan(y) ? 0 : +1;
  if (isnan(y)) return -1;
  if (x < y) return -1;
  if (x > y) return 1;
  if (x != 0) return 0;
  if (signbit(x))
    return signbit(y) ? 0 : -1;
  else
    return signbit(y) ? -1 : 0;
}

static int js_TA_cmp_int8(const void *a, const void *b, void *opaque) {
  return *(const int8_t *)a - *(const int8_t *)b;
}

static int js_TA_cmp_uint8(const void *a, const void *b, void *opaque) {
  return *(const uint8_t *)a - *(const uint8_t *)b;
}

static int js_TA_cmp_int16(const void *a, const void *b, void *opaque) {
  return *(const int16_t *)a - *(const int16_t *)b;
}

static int js_TA_cmp_uint16(const void *a, const void *b, void *opaque) {
  return *(const uint16_t *)a - *(const uint16_t *)b;
}

static int js_TA_cmp_int32(const void *a, const void *b, void *opaque) {
  int32_t x = *(const int32_t *)a;
  int32_t y = *(const int32_t *)b;
  return (y < x) - (y > x);
}

static int js_TA_cmp_uint32(const void *a, const void *b, void *opaque) {
  uint32_t x = *(const uint32_t *)a;
  uint32_t y = *(const uint32_t *)b;
  return (y < x) - (y > x);
}

static int js_TA_cmp_int64(const void *a, const void *b, void *opaque) {
  int64_t x = *(const int64_t *)a;
  int64_t y = *(const int64_t *)b;
  return (y < x) - (y > x);
}

static int js_TA_cmp_uint64(const void *a, const void *b, void *opaque) {
  uint64_t x = *(const uint64_t *)a;
  uint64_t y = *(const uint64_t *)b;
  return (y < x) - (y > x);
}

static int js_TA_cmp_float32(const void *a, const void *b, void *opaque) {
  return js_cmp_doubles(*(const float *)a, *(const float *)b);
}

static int js_TA_cmp_float64(const void *a, const void *b, void *opaque) {
  return js_cmp_doubles(*(const double *)a, *(const double *)b);
}

static LEPUSValue js_TA_get_int8(LEPUSContext *ctx, const void *a) {
  return LEPUS_NewInt32(ctx, *(const int8_t *)a);
}

static LEPUSValue js_TA_get_uint8(LEPUSContext *ctx, const void *a) {
  return LEPUS_NewInt32(ctx, *(const uint8_t *)a);
}

static LEPUSValue js_TA_get_int16(LEPUSContext *ctx, const void *a) {
  return LEPUS_NewInt32(ctx, *(const int16_t *)a);
}

static LEPUSValue js_TA_get_uint16(LEPUSContext *ctx, const void *a) {
  return LEPUS_NewInt32(ctx, *(const uint16_t *)a);
}

static LEPUSValue js_TA_get_int32(LEPUSContext *ctx, const void *a) {
  return LEPUS_NewInt32(ctx, *(const int32_t *)a);
}

static LEPUSValue js_TA_get_uint32(LEPUSContext *ctx, const void *a) {
  return JS_NewUint32(ctx, *(const uint32_t *)a);
}

static LEPUSValue js_TA_get_int64(LEPUSContext *ctx, const void *a) {
  return LEPUS_NewBigInt64(ctx, *(int64_t *)(a));
}

static LEPUSValue js_TA_get_uint64(LEPUSContext *ctx, const void *a) {
  return LEPUS_NewBigUint64(ctx, *(uint64_t *)(a));
}

static LEPUSValue js_TA_get_float32(LEPUSContext *ctx, const void *a) {
  return __JS_NewFloat64(ctx, *(const float *)a);
}

static LEPUSValue js_TA_get_float64(LEPUSContext *ctx, const void *a) {
  return __JS_NewFloat64(ctx, *(const double *)a);
}

struct TA_sort_context {
  LEPUSContext *ctx;
  int exception;
  LEPUSValueConst arr;
  LEPUSValueConst cmp;
  LEPUSValue (*getfun)(LEPUSContext *ctx, const void *a);
  int (*cmpfun)(const void *a, const void *b, void *opaque);
  uint8_t *array_ptr;
  int elt_size;
};

static int js_TA_cmp_generic(const void *a, const void *b, void *opaque) {
  struct TA_sort_context *psc = static_cast<struct TA_sort_context *>(opaque);
  LEPUSContext *ctx = psc->ctx;
  uint32_t a_idx, b_idx;
  LEPUSValueConst argv[2];
  LEPUSValue res;
  int cmp;

  cmp = 0;
  if (!psc->exception) {
    a_idx = *reinterpret_cast<uint32_t *>(const_cast<void *>(a));
    b_idx = *reinterpret_cast<uint32_t *>(const_cast<void *>(b));
    argv[0] = psc->getfun(ctx, psc->array_ptr + a_idx * (size_t)psc->elt_size);
    argv[1] =
        psc->getfun(ctx, psc->array_ptr + b_idx * (size_t)(psc->elt_size));
    res = JS_Call_GC(ctx, psc->cmp, LEPUS_UNDEFINED, 2, argv);
    if (LEPUS_IsException(res)) {
      psc->exception = 1;
      return cmp;
    }
    if (LEPUS_VALUE_IS_INT(res)) {
      int val = LEPUS_VALUE_GET_INT(res);
      cmp = (val > 0) - (val < 0);
    } else {
      double val;
      if (JS_ToFloat64Free(ctx, &val, res) < 0) {
        psc->exception = 1;
        return cmp;
      } else {
        cmp = (val > 0) - (val < 0);
      }
    }
    if (cmp == 0) {
      /* make sort stable: compare array offsets */
      cmp = (a_idx > b_idx) - (a_idx < b_idx);
    }
    if (validate_typed_array(ctx, psc->arr) < 0) {
      psc->exception = 1;
    }
  }
  return cmp;
}

static LEPUSValue js_typed_array_sort(LEPUSContext *ctx,
                                      LEPUSValueConst this_val, int argc,
                                      LEPUSValueConst *argv) {
  HandleScope func_scope(ctx);
  LEPUSObject *p;
  int len, elt_size;
  struct TA_sort_context tsc;
  void *array_ptr, *array_copy = NULL, *array_org;
  int (*cmpfun)(const void *a, const void *b, void *opaque);

  tsc.ctx = ctx;
  tsc.exception = 0;
  tsc.arr = this_val;
  tsc.cmp = argv[0];

  len = js_typed_array_get_length_internal(ctx, this_val);
  if (len < 0) return LEPUS_EXCEPTION;
  if (!LEPUS_IsUndefined(tsc.cmp) && check_function(ctx, tsc.cmp))
    return LEPUS_EXCEPTION;

  if (len > 1) {
    p = LEPUS_VALUE_GET_OBJ(this_val);
    switch (p->class_id) {
      case JS_CLASS_INT8_ARRAY:
        tsc.getfun = js_TA_get_int8;
        tsc.cmpfun = js_TA_cmp_int8;
        break;
      case JS_CLASS_UINT8C_ARRAY:
      case JS_CLASS_UINT8_ARRAY:
        tsc.getfun = js_TA_get_uint8;
        tsc.cmpfun = js_TA_cmp_uint8;
        break;
      case JS_CLASS_INT16_ARRAY:
        tsc.getfun = js_TA_get_int16;
        tsc.cmpfun = js_TA_cmp_int16;
        break;
      case JS_CLASS_UINT16_ARRAY:
        tsc.getfun = js_TA_get_uint16;
        tsc.cmpfun = js_TA_cmp_uint16;
        break;
      case JS_CLASS_INT32_ARRAY:
        tsc.getfun = js_TA_get_int32;
        tsc.cmpfun = js_TA_cmp_int32;
        break;
      case JS_CLASS_UINT32_ARRAY:
        tsc.getfun = js_TA_get_uint32;
        tsc.cmpfun = js_TA_cmp_uint32;
        break;
      case JS_CLASS_BIG_INT64_ARRAY:
        tsc.getfun = js_TA_get_int64;
        tsc.cmpfun = js_TA_cmp_int64;
        break;
      case JS_CLASS_BIG_UINT64_ARRAY:
        tsc.getfun = js_TA_get_uint64;
        tsc.cmpfun = js_TA_cmp_uint64;
        break;
      case JS_CLASS_FLOAT32_ARRAY:
        tsc.getfun = js_TA_get_float32;
        tsc.cmpfun = js_TA_cmp_float32;
        break;
      case JS_CLASS_FLOAT64_ARRAY:
        tsc.getfun = js_TA_get_float64;
        tsc.cmpfun = js_TA_cmp_float64;
        break;
      default:
        abort();
    }
    array_ptr = array_org = p->u.array.u.ptr;
    elt_size = 1 << typed_array_size_log2(p->class_id);
    cmpfun = tsc.cmpfun;
    if (!LEPUS_IsUndefined(tsc.cmp)) {
      uint32_t *array_idx;
      void *array_tmp;
      size_t i, j;

      /* XXX: a stable sort would use less memory */
      array_idx = static_cast<uint32_t *>(
          lepus_malloc(ctx, len * sizeof(array_idx[0]), ALLOC_TAG_WITHOUT_PTR));
      if (!array_idx) return LEPUS_EXCEPTION;
      func_scope.PushHandle(array_idx, HANDLE_TYPE_DIR_HEAP_OBJ);
      for (i = 0; i < len; i++) array_idx[i] = i;
      tsc.array_ptr = static_cast<uint8_t *>(array_ptr);
      tsc.elt_size = elt_size;
      rqsort(array_idx, len, sizeof(array_idx[0]), js_TA_cmp_generic, &tsc);
      if (tsc.exception) goto fail;
      array_tmp = lepus_malloc(ctx, len * elt_size, ALLOC_TAG_WITHOUT_PTR);
      if (!array_tmp) {
      fail:
        return LEPUS_EXCEPTION;
      }
      func_scope.PushHandle(array_tmp, HANDLE_TYPE_DIR_HEAP_OBJ);
      memcpy(array_tmp, array_ptr, len * elt_size);
      switch (elt_size) {
        case 1:
          for (i = 0; i < len; i++) {
            j = array_idx[i];
            (reinterpret_cast<uint8_t *>(array_ptr))[i] =
                (reinterpret_cast<uint8_t *>(array_tmp))[j];
          }
          break;
        case 2:
          for (i = 0; i < len; i++) {
            j = array_idx[i];
            (reinterpret_cast<uint16_t *>(array_ptr))[i] =
                (reinterpret_cast<uint16_t *>(array_tmp))[j];
          }
          break;
        case 4:
          for (i = 0; i < len; i++) {
            j = array_idx[i];
            (reinterpret_cast<uint32_t *>(array_ptr))[i] =
                (reinterpret_cast<uint32_t *>(array_tmp))[j];
          }
          break;
        case 8:
          for (i = 0; i < len; i++) {
            j = array_idx[i];
            (reinterpret_cast<uint64_t *>(array_ptr))[i] =
                (reinterpret_cast<uint64_t *>(array_tmp))[j];
          }
          break;
        default:
          abort();
      }
    } else {
      rqsort(array_ptr, len, elt_size, cmpfun, &tsc);
      if (tsc.exception) {
        return LEPUS_EXCEPTION;
      }
    }
  }
  return this_val;
}

static const LEPUSCFunctionListEntry js_typed_array_base_funcs[] = {
    LEPUS_CFUNC_DEF("from", 1, js_typed_array_from),
    LEPUS_CFUNC_DEF("of", 0, js_typed_array_of),
    LEPUS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL),
    // LEPUS_CFUNC_DEF("__getLength", 2, js_typed_array___getLength ),
    // LEPUS_CFUNC_DEF("__create", 2, js_typed_array___create ),
    // LEPUS_CFUNC_DEF("__speciesCreate", 2, js_typed_array___speciesCreate
    // ),
};

static const LEPUSCFunctionListEntry js_typed_array_base_proto_funcs[] = {
    LEPUS_CGETSET_DEF("length", js_typed_array_get_length, NULL),
    LEPUS_CGETSET_MAGIC_DEF("buffer", js_typed_array_get_buffer, NULL, 0),
    LEPUS_CGETSET_MAGIC_DEF("byteLength", js_typed_array_get_byteLength, NULL,
                            0),
    LEPUS_CGETSET_MAGIC_DEF("byteOffset", js_typed_array_get_byteOffset, NULL,
                            0),
    LEPUS_CFUNC_DEF("set", 1, js_typed_array_set),
    LEPUS_CFUNC_MAGIC_DEF("values", 0, js_create_typed_array_iterator,
                          JS_ITERATOR_KIND_VALUE),
    LEPUS_ALIAS_DEF("[Symbol.iterator]", "values"),
    LEPUS_CFUNC_MAGIC_DEF("keys", 0, js_create_typed_array_iterator,
                          JS_ITERATOR_KIND_KEY),
    LEPUS_CFUNC_MAGIC_DEF("entries", 0, js_create_typed_array_iterator,
                          JS_ITERATOR_KIND_KEY_AND_VALUE),
    LEPUS_CGETSET_DEF("[Symbol.toStringTag]", js_typed_array_get_toStringTag,
                      NULL),
    LEPUS_CFUNC_DEF("copyWithin", 2, js_typed_array_copyWithin),
    LEPUS_CFUNC_MAGIC_DEF("every", 1, js_array_every,
                          special_every | special_TA),
    LEPUS_CFUNC_MAGIC_DEF("some", 1, js_array_every, special_some | special_TA),
    LEPUS_CFUNC_MAGIC_DEF("forEach", 1, js_array_every,
                          special_forEach | special_TA),
    LEPUS_CFUNC_MAGIC_DEF("map", 1, js_array_every, special_map | special_TA),
    LEPUS_CFUNC_MAGIC_DEF("filter", 1, js_array_every,
                          special_filter | special_TA),
    LEPUS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce_gc,
                          special_reduce | special_TA),
    LEPUS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce_gc,
                          special_reduceRight | special_TA),
    LEPUS_CFUNC_DEF("fill", 1, js_typed_array_fill),
    LEPUS_CFUNC_MAGIC_DEF("find", 1, js_typed_array_find, ArrayFind),
    LEPUS_CFUNC_MAGIC_DEF("findIndex", 1, js_typed_array_find, ArrayFindIndex),
    LEPUS_CFUNC_MAGIC_DEF("findLast", 1, js_typed_array_find, ArrayFindLast),
    LEPUS_CFUNC_MAGIC_DEF("findLastIndex", 1, js_typed_array_find,
                          ArrayFindLastIndex),
    LEPUS_CFUNC_DEF("reverse", 0, js_typed_array_reverse),
    LEPUS_CFUNC_DEF("slice", 2, js_typed_array_slice),
    LEPUS_CFUNC_DEF("subarray", 2, js_typed_array_subarray),
    LEPUS_CFUNC_DEF("sort", 1, js_typed_array_sort),
    LEPUS_CFUNC_MAGIC_DEF("join", 1, js_typed_array_join, 0),
    LEPUS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_typed_array_join, 1),
    LEPUS_CFUNC_MAGIC_DEF("indexOf", 1, js_typed_array_indexOf,
                          special_indexOf),
    LEPUS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_typed_array_indexOf,
                          special_lastIndexOf),
    LEPUS_CFUNC_MAGIC_DEF("includes", 1, js_typed_array_indexOf,
                          special_includes),
    // LEPUS_ALIAS_BASE_DEF("toString", "toString", 2 /* Array.prototype. */),
    // @@@
};

static LEPUSValue js_typed_array_base_constructor(LEPUSContext *ctx,
                                                  LEPUSValueConst this_val,
                                                  int argc,
                                                  LEPUSValueConst *argv) {
  return LEPUS_ThrowTypeError(ctx, "cannot be called");
}

/* 'obj' must be an allocated typed array object */
static int typed_array_init(LEPUSContext *ctx, LEPUSValueConst obj,
                            LEPUSValue buffer, uint64_t offset, uint64_t len) {
  JSTypedArray *ta;
  LEPUSObject *p, *pbuffer;
  JSArrayBuffer *abuf;
  int size_log2;

  p = LEPUS_VALUE_GET_OBJ(obj);
  size_log2 = typed_array_size_log2(p->class_id);
  ta = static_cast<JSTypedArray *>(
      lepus_malloc(ctx, sizeof(*ta), ALLOC_TAG_JSTypedArray));
  if (!ta) {
    return -1;
  }
  pbuffer = LEPUS_VALUE_GET_OBJ(buffer);
  abuf = pbuffer->u.array_buffer;
  ta->obj = p;
  ta->buffer = pbuffer;
  ta->offset = offset;
  ta->length = len << size_log2;
  list_add_tail(&ta->link, &abuf->array_list);
  p->u.typed_array = ta;
  p->u.array.count = len;
  p->u.array.u.ptr = abuf->data + offset;
  return 0;
}

static LEPUSValue js_array_from_iterator(LEPUSContext *ctx, uint32_t *plen,
                                         LEPUSValueConst obj,
                                         LEPUSValueConst method) {
  LEPUSValue arr, iter, next_method = LEPUS_UNDEFINED, val = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &val, HANDLE_TYPE_LEPUS_VALUE);
  BOOL done;
  uint32_t k;

  *plen = 0;
  arr = JS_NewArray_GC(ctx);
  if (LEPUS_IsException(arr)) return arr;
  func_scope.PushHandle(&arr, HANDLE_TYPE_LEPUS_VALUE);
  iter = JS_GetIterator2(ctx, obj, method);
  if (LEPUS_IsException(iter)) goto fail;
  func_scope.PushHandle(&iter, HANDLE_TYPE_LEPUS_VALUE);
  next_method = JS_GetPropertyInternal_GC(ctx, iter, JS_ATOM_next, iter, 0);
  if (LEPUS_IsException(next_method)) goto fail;
  func_scope.PushHandle(&next_method, HANDLE_TYPE_LEPUS_VALUE);
  k = 0;
  for (;;) {
    val = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
    if (LEPUS_IsException(val)) goto fail;
    if (done) {
      break;
    }
    if (JS_CreateDataPropertyUint32(ctx, arr, k, val, LEPUS_PROP_THROW) < 0)
      goto fail;
    k++;
  }
  *plen = k;
  return arr;
fail:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_typed_array_constructor_obj(LEPUSContext *ctx,
                                                 LEPUSValueConst new_target,
                                                 LEPUSValueConst obj,
                                                 int classid) {
  LEPUSValue iter, ret, arr = LEPUS_UNDEFINED, val = LEPUS_UNDEFINED, buffer;
  HandleScope func_scope(ctx, &arr, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
  uint32_t i;
  int size_log2;
  int64_t len;

  size_log2 = typed_array_size_log2(classid);
  ret = js_create_from_ctor_GC(ctx, new_target, classid);
  if (LEPUS_IsException(ret)) return LEPUS_EXCEPTION;
  func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);

  iter = JS_GetPropertyInternal_GC(ctx, obj, JS_ATOM_Symbol_iterator, obj, 0);
  if (LEPUS_IsException(iter)) goto fail;
  func_scope.PushHandle(&iter, HANDLE_TYPE_LEPUS_VALUE);
  if (!LEPUS_IsUndefined(iter) && !LEPUS_IsNull(iter)) {
    uint32_t len1;
    arr = js_array_from_iterator(ctx, &len1, obj, iter);
    if (LEPUS_IsException(arr)) goto fail;
    len = len1;
  } else {
    if (js_get_length64(ctx, &len, obj)) goto fail;
    arr = obj;
  }

  buffer = js_array_buffer_constructor1(ctx, LEPUS_UNDEFINED, len << size_log2);
  if (LEPUS_IsException(buffer)) goto fail;
  func_scope.PushHandle(&buffer, HANDLE_TYPE_LEPUS_VALUE);
  if (typed_array_init(ctx, ret, buffer, 0, len)) goto fail;

  for (i = 0; i < len; i++) {
    val = JS_GetPropertyUint32_GC(ctx, arr, i);
    if (LEPUS_IsException(val)) goto fail;
    if (JS_SetPropertyUint32_GC(ctx, ret, i, val) < 0) goto fail;
  }
  return ret;
fail:
  return LEPUS_EXCEPTION;
}

static LEPUSValue js_typed_array_constructor_ta(LEPUSContext *ctx,
                                                LEPUSValueConst new_target,
                                                LEPUSValueConst src_obj,
                                                int classid) {
  LEPUSObject *p, *src_buffer;
  JSTypedArray *ta;
  LEPUSValue ctor, obj, buffer;
  uint32_t len, i;
  int size_log2;
  JSArrayBuffer *src_abuf, *abuf;

  obj = js_create_from_ctor_GC(ctx, new_target, classid);
  if (LEPUS_IsException(obj)) return obj;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  p = LEPUS_VALUE_GET_OBJ(src_obj);
  if (typed_array_is_detached(ctx, p)) {
    JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
    goto fail;
  }
  ta = p->u.typed_array;
  len = p->u.array.count;
  src_buffer = ta->buffer;
  src_abuf = src_buffer->u.array_buffer;
  if (!src_abuf->shared) {
    ctor = JS_SpeciesConstructor(ctx, LEPUS_MKPTR(LEPUS_TAG_OBJECT, src_buffer),
                                 LEPUS_UNDEFINED);
    if (LEPUS_IsException(ctor)) goto fail;
  } else {
    /* force ArrayBuffer default constructor */
    ctor = LEPUS_UNDEFINED;
  }
  func_scope.PushHandle(&ctor, HANDLE_TYPE_LEPUS_VALUE);
  size_log2 = typed_array_size_log2(classid);
  buffer = js_array_buffer_constructor1(ctx, ctor, (uint64_t)len << size_log2);
  if (LEPUS_IsException(buffer)) goto fail;
  func_scope.PushHandle(&buffer, HANDLE_TYPE_LEPUS_VALUE);
  /* necessary because it could have been detached */
  if (typed_array_is_detached(ctx, p)) {
    JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
    goto fail;
  }
  abuf = static_cast<JSArrayBuffer *>(
      LEPUS_GetOpaque(buffer, JS_CLASS_ARRAY_BUFFER));
  if (typed_array_init(ctx, obj, buffer, 0, len)) goto fail;
  if (p->class_id == classid) {
    /* same type: copy the content */
    memcpy(abuf->data, src_abuf->data + ta->offset, abuf->byte_length);
  } else {
    for (i = 0; i < len; i++) {
      HandleScope block_scope(ctx->rt);
      LEPUSValue val;
      val = JS_GetPropertyUint32_GC(ctx, src_obj, i);
      if (LEPUS_IsException(val)) goto fail;
      block_scope.PushHandle(&val, HANDLE_TYPE_LEPUS_VALUE);
      if (JS_SetPropertyUint32_GC(ctx, obj, i, val) < 0) goto fail;
    }
  }
  return obj;
fail:
  return LEPUS_EXCEPTION;
}

LEPUSValue js_typed_array_constructor_GC(LEPUSContext *ctx,
                                         LEPUSValueConst new_target, int argc,
                                         LEPUSValueConst *argv, int classid) {
  LEPUSValue buffer = LEPUS_UNDEFINED, obj;
  HandleScope func_scope(ctx, &buffer, HANDLE_TYPE_LEPUS_VALUE);
  JSArrayBuffer *abuf;
  int size_log2;
  uint64_t len, offset;

  size_log2 = typed_array_size_log2(classid);
  if (LEPUS_VALUE_IS_NOT_OBJECT(argv[0])) {
    if (JS_ToIndex_GC(ctx, &len, argv[0])) return LEPUS_EXCEPTION;
    buffer =
        js_array_buffer_constructor1(ctx, LEPUS_UNDEFINED, len << size_log2);
    if (LEPUS_IsException(buffer)) return LEPUS_EXCEPTION;
    offset = 0;
  } else {
    LEPUSObject *p = LEPUS_VALUE_GET_OBJ(argv[0]);
    if (p->class_id == JS_CLASS_ARRAY_BUFFER ||
        p->class_id == JS_CLASS_SHARED_ARRAY_BUFFER) {
      abuf = p->u.array_buffer;
      if (JS_ToIndex_GC(ctx, &offset, argv[1])) return LEPUS_EXCEPTION;
      if (abuf->detached) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
      if ((offset & ((1 << size_log2) - 1)) != 0 || offset > abuf->byte_length)
        return LEPUS_ThrowRangeError(ctx, "invalid offset");
      if (LEPUS_IsUndefined(argv[2])) {
        if ((abuf->byte_length & ((1 << size_log2) - 1)) != 0)
          goto invalid_length;
        len = (abuf->byte_length - offset) >> size_log2;
      } else {
        if (JS_ToIndex_GC(ctx, &len, argv[2])) return LEPUS_EXCEPTION;
        if (abuf->detached) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
        if ((offset + (len << size_log2)) > abuf->byte_length) {
        invalid_length:
          return LEPUS_ThrowRangeError(ctx, "invalid length");
        }
      }
      buffer = argv[0];
    } else {
      if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
          p->class_id <= JS_CLASS_BIG_UINT64_ARRAY) {
        return js_typed_array_constructor_ta(ctx, new_target, argv[0], classid);
      } else {
        return js_typed_array_constructor_obj(ctx, new_target, argv[0],
                                              classid);
      }
    }
  }

  obj = js_create_from_ctor_GC(ctx, new_target, classid);
  if (LEPUS_IsException(obj)) {
    return LEPUS_EXCEPTION;
  }
  func_scope.PushHandle(&obj, HANDLE_TYPE_LEPUS_VALUE);
  if (typed_array_init(ctx, obj, buffer, offset, len)) {
    return LEPUS_EXCEPTION;
  }
  return obj;
}

void JS_AddIntrinsicTypedArrays_GC(LEPUSContext *ctx) {
  LEPUSValue typed_array_base_proto, typed_array_base_func;
  LEPUSValueConst array_buffer_func, shared_array_buffer_func, obj;
  int i;
  obj = typed_array_base_proto = typed_array_base_func = array_buffer_func =
      shared_array_buffer_func = LEPUS_UNDEFINED;
  HandleScope func_scope(ctx, &obj, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&typed_array_base_proto, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&typed_array_base_func, HANDLE_TYPE_LEPUS_VALUE);
  /* ArrayBuffer */
  JS_NewCConstructor(
      ctx, JS_CLASS_ARRAY_BUFFER, "ArrayBuffer", js_array_buffer_constructor, 1,
      LEPUS_CFUNC_constructor, 0, LEPUS_UNDEFINED, js_array_buffer_funcs,
      countof(js_array_buffer_funcs), js_array_buffer_proto_funcs,
      countof(js_array_buffer_proto_funcs), 0);

  /* SharedArrayBuffer*/
  JS_NewCConstructor(
      ctx, JS_CLASS_SHARED_ARRAY_BUFFER, "SharedArrayBuffer",
      js_shared_array_buffer_constructor, 1, LEPUS_CFUNC_constructor, 0,
      LEPUS_UNDEFINED, js_shared_array_buffer_funcs,
      countof(js_shared_array_buffer_funcs), js_shared_array_buffer_proto_funcs,
      countof(js_shared_array_buffer_proto_funcs), 0);

  /* TypedArray */
  typed_array_base_func = JS_NewCConstructor(
      ctx, -1, "TypedArray", js_typed_array_base_constructor, 0,
      LEPUS_CFUNC_constructor_or_func, 0, LEPUS_UNDEFINED,
      js_typed_array_base_funcs, countof(js_typed_array_base_funcs),
      js_typed_array_base_proto_funcs, countof(js_typed_array_base_proto_funcs),
      JS_NEW_CTOR_NO_GLOBAL);

  /* TypedArray.prototype.toString must be the same object as
   * Array.prototype.toString */
  obj = JS_GetPropertyInternal_GC(ctx, ctx->class_proto[JS_CLASS_ARRAY],
                                  JS_ATOM_toString,
                                  ctx->class_proto[JS_CLASS_ARRAY], 0);
  /* XXX: should use alias method in LEPUSCFunctionListEntry */  //@@@
  typed_array_base_proto =
      LEPUS_GetProperty(ctx, typed_array_base_func, JS_ATOM_prototype);
  JS_DefinePropertyValue_GC(ctx, typed_array_base_proto, JS_ATOM_toString, obj,
                            LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE);

  auto add_intrinsic_typed_arrays = [&](LEPUSClassID i, LEPUSAtom atom) {
    char buf[ATOM_GET_STR_BUF_SIZE];
    const char *name;
    const LEPUSCFunctionListEntry *bpe;
    LEPUSCFunctionType ft2 = {.generic_magic = js_typed_array_constructor_GC};
    name = JS_AtomGetStr(ctx, buf, sizeof(buf), atom);

    bpe = js_typed_array_funcs + typed_array_size_log2(i);

    JS_NewCConstructor(ctx, i, name, ft2.generic, 3,
                       LEPUS_CFUNC_constructor_magic, i, typed_array_base_func,
                       bpe, 1, bpe, 1, 0);
  };

  for (i = JS_CLASS_UINT8C_ARRAY;
       i < JS_CLASS_UINT8C_ARRAY + JS_TYPED_ARRAY_COUNT - 2; i++) {
    add_intrinsic_typed_arrays(
        i, JS_ATOM_Uint8ClampedArray + i - JS_CLASS_UINT8C_ARRAY);
  }

  /* BigInt64 BigUint64 */
  LEPUSAtom bigint64array_atom =
      ctx->rt->class_array[JS_CLASS_BIG_INT64_ARRAY].class_name;
  for (i = JS_CLASS_BIG_INT64_ARRAY; i <= JS_CLASS_BIG_UINT64_ARRAY; ++i) {
    add_intrinsic_typed_arrays(
        i, bigint64array_atom + (i - JS_CLASS_BIG_INT64_ARRAY));
  }

  /* DataView */
  JS_NewCConstructor(ctx, JS_CLASS_DATAVIEW, "DataView",
                     js_dataview_constructor, 1, LEPUS_CFUNC_constructor, 0,
                     LEPUS_UNDEFINED, nullptr, 0, js_dataview_proto_funcs,
                     countof(js_dataview_proto_funcs), 0);
  /* Atomics */
#ifdef CONFIG_ATOMICS
  JS_AddIntrinsicAtomics(ctx);
#endif
}

LEPUSValue JS_NewWString_GC(LEPUSContext *ctx, const uint16_t *buf,
                            size_t length) {
  return js_new_string16(ctx, buf, length);
}

const uint16_t *JS_GetStringChars_GC(LEPUSContext *ctx, LEPUSValueConst str) {
  if (JS_IsSeparableString(str)) {
    str = JS_GetSeparableStringContentNotDup_GC(ctx, str);
  }

  if (!LEPUS_VALUE_IS_STRING(str)) return 0;
  JSString *s = LEPUS_VALUE_GET_STRING(str);
  if (!s->is_wide_char) return 0;
  return s->u.str16;
}

LEPUSValue JS_NewArrayWithValue_GC(LEPUSContext *ctx, uint32_t length,
                                   LEPUSValueConst *values) {
  return js_build_rest_gc(ctx, 0, length, values);
}

LEPUSValue JS_NewTypedArray_GC(LEPUSContext *ctx, uint32_t length,
                               LEPUSClassID class_id) {
  LEPUSValue arg0 = LEPUS_NewInt32(ctx, length);
  HandleScope func_scope(ctx, &arg0, HANDLE_TYPE_LEPUS_VALUE);
  return js_typed_array_constructor_GC(ctx, LEPUS_UNDEFINED, 1, &arg0,
                                       class_id);
}

LEPUSValue JS_NewTypedArrayWithBuffer_GC(LEPUSContext *ctx,
                                         LEPUSValueConst buffer,
                                         uint32_t byteOffset, uint32_t length,
                                         LEPUSClassID class_id) {
  LEPUSValue args[3] = {buffer, LEPUS_NewInt32(ctx, byteOffset),
                        LEPUS_NewInt32(ctx, length)};
  return js_typed_array_constructor_GC(ctx, LEPUS_UNDEFINED, 3, args, class_id);
}

LEPUSValue JS_CallConstructorV_GC(LEPUSContext *ctx, LEPUSValueConst func_obj,
                                  int argc, LEPUSValue *argv) {
  return JS_CallConstructorInternal_GC(ctx, func_obj, func_obj, argc, argv, 0);
}

/* return JS_ATOM_NULL in case of exception */
JSAtom JS_ValueToAtom_GC(LEPUSContext *ctx, LEPUSValueConst val) {
  JSAtom atom;
  if (LEPUS_VALUE_IS_INT(val) &&
      (uint32_t)LEPUS_VALUE_GET_INT(val) <= JS_ATOM_MAX_INT) {
    /* fast path for integer values */
    atom = __JS_AtomFromUInt32(LEPUS_VALUE_GET_INT(val));
  } else if (LEPUS_VALUE_IS_SYMBOL(val)) {
    JSAtomStruct *p = static_cast<JSAtomStruct *>(LEPUS_VALUE_GET_PTR(val));
    atom = js_get_atom_index(ctx->rt, p);
  } else {
    HandleScope block_scope(ctx->rt);
    LEPUSValue str;
    str = JS_ToPropertyKey_GC(ctx, val);
    if (LEPUS_IsException(str)) return JS_ATOM_NULL;
    block_scope.PushHandle(&str, HANDLE_TYPE_LEPUS_VALUE);
    if (LEPUS_VALUE_IS_SYMBOL(str)) {
      atom = js_symbol_to_atom(ctx, str);
    } else {
      atom = JS_NewAtomStr(ctx, LEPUS_VALUE_GET_STRING(str));
    }
  }
  return atom;
}

// <Primjs begin>
class ObjectCloneStateGC {
 public:
  using key_t = LEPUSObject *;
  using val_t = LEPUSObject *;

  explicit ObjectCloneStateGC(LEPUSContext *ctx) : ctx_(ctx) {
    handled_obj_ = JS_NewObject_GC(ctx_);
  }

  ~ObjectCloneStateGC() {}

  LEPUSValue *GetObjectPtr() { return &handled_obj_; }

  val_t GetVal(const key_t &key) const {
    auto ret = JS_GetPropertyInt64(ctx_, handled_obj_, (int64_t)key);
    if (LEPUS_IsUndefined(ret)) {
      return nullptr;
    }
    int64_t val = 0;
    if (JS_ToInt64_GC(ctx_, &val, ret)) {
      return nullptr;
    }
    return (val_t)val;
  }

  void SetValue(const key_t &key, const val_t &val) {
    HandleScope func_scope(ctx_->rt);
    LEPUSValue num_val = JS_NewInt64_GC(ctx_, (int64_t)val);
    func_scope.PushHandle(&num_val, HANDLE_TYPE_LEPUS_VALUE);
    JS_SetPropertyInt64_GC(ctx_, handled_obj_, (int64_t)key, num_val);
  }

  void EraseKey(const key_t &key) {
    JS_DeletePropertyInt64(ctx_, handled_obj_, (int64_t)key, 0);
  }

 private:
  LEPUSContext *ctx_ = nullptr;
  LEPUSValue handled_obj_ = LEPUS_UNDEFINED;
  ObjectCloneStateGC(const ObjectCloneStateGC &) {}
  ObjectCloneStateGC(ObjectCloneStateGC &&) {}
};

static LEPUSValue JS_StructuredClone(LEPUSContext *ctx, LEPUSValue src,
                                     ObjectCloneStateGC &state) {
  auto tag = LEPUS_VALUE_GET_NORM_TAG(src);
  LEPUSValue *state_obj = state.GetObjectPtr();
  HandleScope func_scope(ctx, state_obj, HANDLE_TYPE_LEPUS_VALUE);

  switch (tag) {
    case LEPUS_TAG_NULL:
    case LEPUS_TAG_UNDEFINED:
    case LEPUS_TAG_BOOL:
    case LEPUS_TAG_INT:
    case LEPUS_TAG_FLOAT64: {
      return src;
    }

#ifdef ENABLE_LEPUSNG
    case LEPUS_TAG_LEPUS_REF: {
      LEPUSLepusRef *pref =
          reinterpret_cast<LEPUSLepusRef *>(LEPUS_VALUE_GET_PTR(src));

      if (auto *p = state.GetVal(reinterpret_cast<LEPUSObject *>(pref->p))) {
        return LEPUS_MKPTR(LEPUS_TAG_OBJECT, p);
      }
      LEPUSValue src_val = JSRef2Value(ctx, src);
      func_scope.PushHandle(&src_val, HANDLE_TYPE_LEPUS_VALUE);
      LEPUSValue ret = JS_StructuredClone(ctx, src_val, state);
      func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);
      if (!LEPUS_IsException(ret)) {
        state.SetValue(reinterpret_cast<LEPUSObject *>(pref->p),
                       LEPUS_VALUE_GET_OBJ(ret));
      }
      return ret;
    }
#endif

    case LEPUS_TAG_OBJECT: {
      LEPUSObject *p = LEPUS_VALUE_GET_OBJ(src);
      if (auto *result = state.GetVal(p)) {
        // find
        return LEPUS_MKPTR(LEPUS_TAG_OBJECT, result);
      }
      LEPUSClassID class_id = p->class_id;
      LEPUSObject *proto = nullptr;
      JSShape *sh = nullptr;
      LEPUSValue ret = LEPUS_UNDEFINED;

      if (!deepclone_opt_disabled(ctx->rt)) {
        if (class_id != JS_CLASS_PROMISE &&
            ((class_id >= JS_CLASS_C_FUNCTION &&
              class_id <= JS_CLASS_GENERATOR_FUNCTION) ||
             (class_id >= JS_CLASS_MAP_ITERATOR &&
              class_id <= JS_CLASS_ASYNC_GENERATOR) ||
             class_id >= JS_CLASS_INIT_COUNT)) {
          return src;
        }
        sh = p->shape;
        proto = sh->proto;
        ret = JS_NewObjectProtoClass_GC(
            ctx, LEPUS_MKPTR(LEPUS_TAG_OBJECT, proto), class_id);
      } else {
        sh = p->shape;
        ret = JS_NewObjectClass_GC(ctx, class_id);
      }
      func_scope.PushHandle(&ret, HANDLE_TYPE_LEPUS_VALUE);

      LEPUSObject *ret_p = LEPUS_VALUE_GET_OBJ(ret);
      state.SetValue(p, ret_p);
      if (sh && p->prop) {
        // clone property
        LEPUSValue cloned_prop = LEPUS_UNDEFINED;
        HandleScope block_scope(ctx, &cloned_prop, HANDLE_TYPE_LEPUS_VALUE);
        JSShapeProperty *prs = sh->prop;
        for (uint32_t i = 0; i < sh->prop_count; ++i, ++prs) {
          int32_t flags = prs->flags;
          if (prs->atom != JS_ATOM_NULL && (flags & LEPUS_PROP_TMASK) == 0) {
            cloned_prop = JS_StructuredClone(ctx, p->prop[i].u.value, state);
            if (LEPUS_IsException(cloned_prop) ||
                (JS_DefinePropertyValue_GC(ctx, ret, prs->atom, cloned_prop,
                                           flags & LEPUS_PROP_C_W_E) < 0)) {
              goto fail;
            }
          }
        }
      }

      switch (class_id) {
        case JS_CLASS_OBJECT:
        case JS_CLASS_ERROR: {
          break;
        }
        case JS_CLASS_ARRAY: {
          if (p->fast_array) {
            LEPUSValue cloned_element = LEPUS_UNDEFINED;
            HandleScope block_scope(ctx, &cloned_element,
                                    HANDLE_TYPE_LEPUS_VALUE);
            for (uint32_t i = 0; i < p->u.array.count; ++i) {
              cloned_element =
                  JS_StructuredClone(ctx, p->u.array.u.values[i], state);
              if (LEPUS_IsException(cloned_element) ||
                  (JS_DefinePropertyValueUint32_GC(ctx, ret, i, cloned_element,
                                                   LEPUS_PROP_C_W_E) < 0)) {
                goto fail;
              }
            }
          }
        } break;
        case JS_CLASS_REGEXP: {
          ret_p->u.regexp.pattern = LEPUS_VALUE_GET_STRING(JS_StructuredClone(
              ctx, LEPUS_MKPTR(LEPUS_TAG_STRING, p->u.regexp.pattern), state));
          ret_p->u.regexp.bytecode = LEPUS_VALUE_GET_STRING(JS_StructuredClone(
              ctx, LEPUS_MKPTR(LEPUS_TAG_STRING, p->u.regexp.bytecode), state));
        } break;
        case JS_CLASS_NUMBER:
        case JS_CLASS_STRING:
        case JS_CLASS_BOOLEAN:
        case JS_CLASS_DATE: {
          LEPUSValue object_data =
              JS_StructuredClone(ctx, p->u.object_data, state);
          JS_SetObjectData(ctx, ret, object_data);
        } break;
        case JS_CLASS_PROMISE: {
          JSPromiseData *s = static_cast<JSPromiseData *>(
              lepus_mallocz(ctx, sizeof(*s), ALLOC_TAG_JSPromiseData));
          if (!s) goto fail;
          s->promise_state = JS_PROMISE_PENDING;
          s->is_handled = FALSE;
          for (int i = 0; i < 2; i++) init_list_head(&s->promise_reactions[i]);
          s->promise_result = LEPUS_UNDEFINED;
          LEPUS_SetOpaque(ret, s);
          HandleScope block_scope(ctx);
          LEPUSValue args[2];
          block_scope.PushLEPUSValueArrayHandle(args, 2);
          if (js_create_resolving_functions(ctx, args, ret)) goto fail;

          LEPUSValue executor =
              js_promise_then_for_deepcopy(ctx, src, 2, args, &state);
          block_scope.PushHandle(&executor, HANDLE_TYPE_LEPUS_VALUE);

          if (LEPUS_IsException(executor)) {
            LEPUSValue res2, error;
            error = LEPUS_GetException(ctx);
            block_scope.PushHandle(&error, HANDLE_TYPE_LEPUS_VALUE);
            res2 = JS_Call_GC(ctx, args[1], LEPUS_UNDEFINED, 1,
                              reinterpret_cast<LEPUSValueConst *>(&error));
            if (LEPUS_IsException(res2)) {
              goto fail;
            }
          }
        } break;
        case JS_CLASS_ARRAY_BUFFER: {
          auto *array_buffer = p->u.array_buffer;
          if (array_buffer && !array_buffer->detached) {
            auto *abuf = reinterpret_cast<JSArrayBuffer *>(lepus_mallocz(
                ctx, sizeof(JSArrayBuffer), ALLOC_TAG_JSArrayBuffer));
            if (!abuf) goto fail;
            func_scope.PushHandle(abuf, HANDLE_TYPE_DIR_HEAP_OBJ);
            abuf->byte_length = array_buffer->byte_length;
            abuf->data = reinterpret_cast<uint8_t *>(
                lepus_malloc(ctx, abuf->byte_length, ALLOC_TAG_WITHOUT_PTR));
            if (!abuf->data) {
              goto fail;
            }
            abuf->free_func = nullptr;
            abuf->from_js_heap = array_buffer->from_js_heap;
            abuf->opaque = array_buffer->opaque;
            init_list_head(&abuf->array_list);
            memcpy(abuf->data, array_buffer->data, abuf->byte_length);
            LEPUS_SetOpaque(ret, abuf);
          }
        } break;
        case JS_CLASS_UINT8C_ARRAY ... JS_CLASS_DATAVIEW: {
          auto *typearray = p->u.typed_array;
          auto *arraybuffer =
              typearray ? typearray->buffer->u.array_buffer : nullptr;
          if (arraybuffer && !arraybuffer->detached) {
            auto *ta = reinterpret_cast<JSTypedArray *>(lepus_mallocz(
                ctx, sizeof(JSTypedArray), ALLOC_TAG_JSTypedArray));
            if (!ta) goto fail;
            func_scope.PushHandle(ta, HANDLE_TYPE_DIR_HEAP_OBJ);
            ta->obj = ret_p;
            ta->offset = typearray->offset;
            ta->length = typearray->length;
            LEPUSValue new_buffer = JS_StructuredClone(
                ctx, LEPUS_MKPTR(LEPUS_TAG_OBJECT, typearray->buffer), state);

            if (LEPUS_IsException(new_buffer)) {
              goto fail;
            }
            auto *pbuffer = LEPUS_VALUE_GET_OBJ(new_buffer);
            ta->buffer = pbuffer;
            auto *abuf = pbuffer->u.array_buffer;
            list_add_tail(&ta->link, &(abuf->array_list));

            ret_p->u.typed_array = ta;
            if (class_id != JS_CLASS_DATAVIEW) {
              ret_p->u.array.count = p->u.array.count;
              ret_p->u.array.u.ptr = abuf->data + ta->offset;
            }
          }
        } break;
        case JS_CLASS_MAP ... JS_CLASS_WEAKSET: {
          if (!deepclone_opt_disabled(ctx->rt)) {
            uint8_t magic = class_id - JS_CLASS_MAP;
            bool is_set = magic & MAGIC_SET;
            auto map_state = p->u.map_state;
            auto *ms = reinterpret_cast<JSMapState *>(
                lepus_mallocz(ctx, sizeof(JSMapState), ALLOC_TAG_JSMapState));
            if (!ms) goto fail;
            init_list_head(&ms->records);
            LEPUS_SetOpaque(ret, ms);
            ms->is_weak = map_state->is_weak;
            ms->record_count = 0;
            ms->hash_size = 1;
            ms->hash_table = static_cast<struct list_head *>(
                lepus_malloc(ctx, sizeof(ms->hash_table[0]) * ms->hash_size,
                             ALLOC_TAG_WITHOUT_PTR));
            if (!ms->hash_table) goto fail;
            init_list_head(&ms->hash_table[0]);
            ms->record_count_threshold = 4;
            LEPUSValue adder = JS_GetPropertyInternal_GC(
                ctx, ret, is_set ? JS_ATOM_add : JS_ATOM_set, ret, 0);
            if (unlikely(LEPUS_IsException(adder))) goto fail;
            if (unlikely(!LEPUS_IsFunction(ctx, adder))) {
              LEPUS_ThrowTypeError(ctx, "set/add is not a function");
              goto fail1;
            }
            list_head *el;
            JSMapRecord *rec;
            LEPUSValue res;
            list_for_each(el, &map_state->records) {
              rec = list_entry(el, JSMapRecord, link);
              if (rec->empty) continue;
              LEPUSValue ms_key = JS_StructuredClone(ctx, rec->key, state);
              if (LEPUS_IsException(ms_key)) {
                goto fail1;
              }
              HandleScope block_scope(ctx, &ms_key, HANDLE_TYPE_LEPUS_VALUE);
              LEPUSValue ms_value = LEPUS_UNDEFINED;
              block_scope.PushHandle(&ms_value, HANDLE_TYPE_LEPUS_VALUE);
              if (!is_set) {
                LEPUSValue args[2];
                args[0] = ms_key;
                ms_value = JS_StructuredClone(ctx, rec->value, state);
                if (LEPUS_IsException(ms_value)) {
                  goto fail1;
                }
                args[1] = ms_value;

                res = JS_Call_GC(ctx, adder, ret, 2, args);
              } else {
                res = JS_Call_GC(ctx, adder, ret, 1, &ms_key);
              }
              if (unlikely(LEPUS_IsException(res))) goto fail1;
            }
            break;
          fail1:
            goto fail;
          } else {
            LEPUS_ThrowTypeError(
                ctx, "object classid: %d is not supported in StructuredClone",
                p->class_id);
            goto fail;
          }
        }
        default: {
          LEPUS_ThrowTypeError(
              ctx, "object classid: %d is not supported in StructuredClone",
              p->class_id);
          goto fail;
        }
      }
      return ret;
    fail:
      state.EraseKey(p);
      return LEPUS_EXCEPTION;
    } break;
    default: {
      goto use_snapshot;
    }

    use_snapshot:
#ifndef NO_QUICKJS_COMPILER
      size_t psize = 0;
      uint8_t *buf = LEPUS_WriteObject(ctx, &psize, src, 0);

      if (buf) {
        HandleScope block_scope(ctx->rt);
        block_scope.PushHandle(buf, HANDLE_TYPE_DIR_HEAP_OBJ);
        LEPUSValue val = LEPUS_ReadObject(ctx, buf, psize, 0);
        return val;
      } else {
        return LEPUS_UNDEFINED;
      }
#else
      return LEPUS_UNDEFINED;
#endif
  }
}

LEPUSValue JS_DeepCopy_GC(LEPUSContext *ctx, LEPUSValueConst obj) {
  ObjectCloneStateGC state(ctx);
  return JS_StructuredClone(ctx, obj, state);
}

LEPUSValue JS_NewObjectWithArgs_GC(LEPUSContext *ctx, int32_t size,
                                   const char **keys, LEPUSValue *values) {
  auto ret = JS_NewObject_GC(ctx);
  if (LEPUS_VALUE_IS_NOT_OBJECT(ret)) return LEPUS_EXCEPTION;
  HandleScope func_scope(ctx, &ret, HANDLE_TYPE_LEPUS_VALUE);
  auto *p = LEPUS_VALUE_GET_OBJ(ret);

  for (int32_t i = 0; i < size; ++i) {
    auto atom = LEPUS_NewAtom(ctx, keys[i]);
    func_scope.PushLEPUSAtom(atom);
    auto *prop = add_property_gc(ctx, p, atom, LEPUS_PROP_C_W_E);
    if (prop) {
      prop->u.value = values[i];
    } else {
      return LEPUS_EXCEPTION;
    }
  }
  return ret;
}

LEPUSValue JS_NewArrayWithArgs_GC(LEPUSContext *ctx, int32_t size,
                                  LEPUSValue *values) {
  auto array = JS_NewArray_GC(ctx);
  HandleScope func_scope(ctx, &array, HANDLE_TYPE_LEPUS_VALUE);
  if (size == 0) return array;
  LEPUSObject *p = nullptr;
  if (!JS_IsArray_GC(ctx, array)) goto failed;
  p = LEPUS_VALUE_GET_OBJ(array);

  p->u.array.u.values = reinterpret_cast<LEPUSValue *>(
      lepus_malloc(ctx, sizeof(LEPUSValue) * size, ALLOC_TAG_WITHOUT_PTR));
  if (p->u.array.u.values == nullptr) goto failed;

  for (int32_t i = 0; i < size; ++i) {
    p->u.array.u.values[i] = values[i];
  }
  p->u.array.count = size;
  p->u.array.u1.size = size;

  // length prop
  p->prop[0].u.value = LEPUS_NewInt32(ctx, size);
  return array;
failed:
  return LEPUS_EXCEPTION;
}

// <primjs begin>
#ifdef ENABLE_PRIMJS_SNAPSHOT

no_inline void prim_js_print_gc(const char *msg) { printf("msg: %s\n", msg); }

static int trace_id = 0;

void prim_js_print_trace_gc(int bytecode, int tos) {
  const char *name = short_opcode_info((OPCodeEnum)bytecode).name;
  PRIM_LOG("TRACE: id: %d, opcode %d, %s tos %d\n", trace_id++, bytecode, name,
           tos);
}

no_inline void prim_js_print_func_gc(LEPUSContext *ctx, LEPUSValue func_obj) {
  if (LEPUS_VALUE_IS_OBJECT(func_obj)) {
    LEPUSObject *p = LEPUS_VALUE_GET_OBJ(func_obj);
    LEPUSFunctionBytecode *b = p->u.func.function_bytecode;
    if (p->class_id == JS_CLASS_BYTECODE_FUNCTION) {
      const char *func_name = get_func_name(ctx, func_obj);
      if (!func_name || func_name[0] == '\0') {
        PRIM_LOG("primjs call bytecode function: \n");
      } else {
        PRIM_LOG("primjs call bytecode function: %s\n", func_name);
      }
    }
  }
}

LEPUSValue prim_js_op_eval_gc(LEPUSContext *ctx, int scope_idx,
                              LEPUSValue op1) {
  LEPUSValue ret_val;

  ret_val = JS_EvalObject(ctx, LEPUS_UNDEFINED, op1, LEPUS_EVAL_TYPE_DIRECT,
                          scope_idx);
  return ret_val;
}

void prim_WriteBarrierNoStore(LEPUSValue value, LEPUSContext *ctx) {
  // GC_TODO
}

void prim_HeapObjStoreLEPUSValue(void *fieldAddr, LEPUSValue value) {
  *reinterpret_cast<LEPUSValue *>(fieldAddr) = value;
}

void prim_HeapObjStorePtr(void *dstObj, address_t offset, void *value) {
  void *fieldAddr = (void *)((address_t)dstObj + offset);
  *reinterpret_cast<address_t *>(fieldAddr) = (address_t)value;
}

void prim_close_var_refs_gc(LEPUSContext *ctx, LEPUSStackFrame *sf) {
  list_head *el;
  JSVarRef *var_ref;
  list_for_each(el, &sf->var_ref_list) {
    var_ref = list_entry(el, JSVarRef, link);
    var_ref->is_detached = 1;
    var_ref->value = *var_ref->pvalue;
    var_ref->pvalue = &var_ref->value;
  }
  return;
}

int prim_js_copy_data_properties_gc(LEPUSContext *ctx, LEPUSValue *sp,
                                    int mask) {
  if (JS_CopyDataProperties(ctx, sp[-1 - (mask & 3)],
                            sp[-1 - ((mask >> 2) & 7)],
                            sp[-1 - ((mask >> 5) & 7)], 0)) {
    return -1;
  }
  return 0;
}

int prim_js_with_op_gc(LEPUSContext *ctx, LEPUSValue *sp, JSAtom atom,
                       int is_with, int opcode) {
  LEPUSValue obj, val;
  obj = sp[-1];
  int ret = LEPUS_HasProperty(ctx, obj, atom);
  if (unlikely(ret < 0)) goto exception;
  if (ret) {
    if (is_with) {
      ret = js_has_unscopable(ctx, obj, atom);
      if (unlikely(ret < 0)) goto exception;
      if (ret) goto no_with;
    }
    switch (opcode) {
      case OP_with_get_var:
        val = JS_GetPropertyInternal_GC(ctx, obj, atom, obj, 0);
        if (unlikely(LEPUS_IsException(val))) goto exception;
        set_value_gc(ctx, &sp[-1], val);
        break;
      case OP_with_put_var:
        ret = JS_SetPropertyInternal_GC(ctx, obj, atom, sp[-2],
                                        LEPUS_PROP_THROW_STRICT);
        if (unlikely(ret < 0)) goto exception;
        break;
      case OP_with_delete_var:
        ret = LEPUS_DeleteProperty(ctx, obj, atom, 0);
        if (unlikely(ret < 0)) goto exception;
        sp[-1] = LEPUS_NewBool(ctx, ret);
        break;
      case OP_with_make_ref:
        /* produce a pair object/propname on the stack */
        *sp++ = JS_AtomToValue_GC(ctx, atom);
        break;
      case OP_with_get_ref:
        /* produce a pair object/method on the stack */
        val = JS_GetPropertyInternal_GC(ctx, obj, atom, obj, 0);
        if (unlikely(LEPUS_IsException(val))) goto exception;
        *sp++ = val;
        break;
      case OP_with_get_ref_undef:
        /* produce a pair undefined/function on the stack */
        val = JS_GetPropertyInternal_GC(ctx, obj, atom, obj, 0);
        if (unlikely(LEPUS_IsException(val))) goto exception;
        sp[-1] = LEPUS_UNDEFINED;
        *sp++ = val;
        break;
    }
    return 1;
  } else {
  no_with:
    /* if not jumping, drop the object argument */
    return 0;
  }
exception:
  return -1;
}

LEPUSValue prim_js_for_in_start_gc(LEPUSContext *ctx, LEPUSValue op) {
  LEPUSValue res = build_for_in_iterator(ctx, op);
  return res;
}

LEPUSValue *prim_js_iterator_close_return_gc(LEPUSContext *ctx,
                                             LEPUSValue *sp) {
  LEPUSValue ret_val;
  /* iter_obj next catch_offset ... ret_val ->
      ret_eval iter_obj next catch_offset */
  ret_val = *--sp;
  while (!LEPUS_VALUE_IS_CATCH_OFFSET(sp[-1])) {
    sp--;
  }
  // TODO(wangao): Add the sp check.
  // if (unlikely(sp < stack_buf + 3)) {
  //   LEPUS_ThrowInternalError(ctx, "iterator_close_return");
  //   LEPUS_FreeValue(ctx, ret_val);
  //   goto exception;
  // }
  sp[0] = sp[-1];
  sp[-1] = sp[-2];
  sp[-2] = sp[-3];
  sp[-3] = ret_val;
  sp++;
  return sp;
}

int prim_js_async_iterator_close_gc(LEPUSContext *ctx, LEPUSValue *sp) {
  LEPUSValue ret, method;
  int ret_flag;
  method = JS_GetPropertyInternal_GC(ctx, sp[-3], JS_ATOM_return, sp[-3], 0);
  if (LEPUS_IsException(method)) goto exception;
  if (LEPUS_IsUndefined(method) || LEPUS_IsNull(method)) {
    ret = LEPUS_UNDEFINED;
    ret_flag = TRUE;
  } else {
    HandleScope block_scope(ctx, &method, HANDLE_TYPE_LEPUS_VALUE);
    ret = JS_CallFree_GC(ctx, method, sp[-3], 0, NULL);
    if (LEPUS_IsException(ret)) goto exception;
    ret_flag = FALSE;
  }
  sp[-3] = ret;
  sp[-2] = LEPUS_NewBool(ctx, ret_flag);
  return 0;
exception:
  return -1;
}

int prim_js_async_iterator_get_gc(LEPUSContext *ctx, LEPUSValue *sp,
                                  int flags) {
  LEPUSValue method, ret;
  BOOL ret_flag;
  if (flags == 2) {
    LEPUS_ThrowTypeError(ctx, "iterator does not have a throw method");
    goto exception;
  }
  method = JS_GetPropertyInternal_GC(
      ctx, sp[-4], flags ? JS_ATOM_throw : JS_ATOM_return, sp[-4], 0);
  if (LEPUS_IsException(method)) goto exception;
  if (LEPUS_IsUndefined(method) || LEPUS_IsNull(method)) {
    ret_flag = TRUE;
  } else {
    HandleScope block_scope(ctx, &method, HANDLE_TYPE_LEPUS_VALUE);
    ret = JS_CallFree_GC(ctx, method, sp[-4], 1,
                         reinterpret_cast<LEPUSValueConst *>(sp - 1));
    if (LEPUS_IsException(ret)) goto exception;
    sp[-1] = ret;
    ret_flag = FALSE;
  }
  sp[0] = LEPUS_NewBool(ctx, ret_flag);
  // sp += 1;
  return 0;
exception:
  return -1;
}

LEPUSValue primjs_get_super_ctor_gc(LEPUSContext *ctx, LEPUSValue op) {
  LEPUSValue proto;
  proto = JS_GetPrototype_GC(ctx, op);
  if (LEPUS_IsException(proto)) {
    return LEPUS_EXCEPTION;
  }
  if (!LEPUS_IsConstructor(ctx, proto)) {
    LEPUS_ThrowTypeError(ctx, "not a constructor");
    return LEPUS_EXCEPTION;
  }
  return proto;
}

LEPUSValue prim_js_unary_arith_slow_gc(LEPUSContext *ctx, LEPUSValue op1,
                                       OPCodeEnum op) {
  int v;
  int64_t tag;
  JSBigIntBuf buf1;
  JSBigInt *p1;
  HandleScope func_scope(ctx, &op1, HANDLE_TYPE_LEPUS_VALUE);
  /* fast path for float64 */
  if (LEPUS_TAG_IS_FLOAT64(LEPUS_VALUE_GET_TAG(op1))) goto handle_float64;
  op1 = JS_ToNumericFree(ctx, op1);
  if (LEPUS_IsException(op1)) goto exception;
  tag = LEPUS_VALUE_GET_TAG(op1);
  switch (tag) {
    case LEPUS_TAG_INT: {
      int64_t v64;
      v64 = LEPUS_VALUE_GET_INT(op1);
      switch (op) {
        case OP_inc:
        case OP_dec:
          v = 2 * (op - OP_dec) - 1;
          v64 += v;
          break;
        case OP_plus:
          break;
        case OP_neg:
          if (v64 == 0) {
            return __JS_NewFloat64(ctx, -0.0);
          } else {
            v64 = -v64;
          }
          break;
        default:
          abort();
      }
      return LEPUS_NewInt64(ctx, v64);
    } break;
    case LEPUS_TAG_BIG_INT: {
      JSBigInt *r;
      p1 = (JSBigInt *)LEPUS_VALUE_GET_PTR(op1);
      switch (op) {
        case OP_plus:
          LEPUS_ThrowTypeError(ctx, "bigint argument with unary +");
          goto exception;
        case OP_inc:
        case OP_dec: {
          JSBigIntBuf buf2;
          JSBigInt *p2;
          p2 = js_bigint_set_si(&buf2, 2 * (op - OP_dec) - 1);
          r = js_bigint_add(ctx, p1, p2, 0);
        } break;
        case OP_neg:
          r = js_bigint_neg(ctx, p1);
          break;
        case OP_not:
          r = js_bigint_not(ctx, p1);
          break;
        default:
          abort();
      }
      if (!r) goto exception;
      return JS_CompactBigInt(ctx, r);
    } break;
    default:
    handle_float64: {
      double d;
      d = LEPUS_VALUE_GET_FLOAT64(op1);
      switch (op) {
        case OP_inc:
        case OP_dec:
          v = 2 * (op - OP_dec) - 1;
          d += v;
          break;
        case OP_plus:
          break;
        case OP_neg:
          d = -d;
          break;
        default:
          abort();
      }
      return __JS_NewFloat64(ctx, d);
    } break;
  }
exception:
  return LEPUS_EXCEPTION;
}

LEPUSValue prim_js_add_slow_gc(LEPUSContext *ctx, LEPUSValue op1,
                               LEPUSValue op2) {
  int64_t tag1, tag2;
  double d1, d2;
  HandleScope func_scope(ctx, &op1, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&op2, HANDLE_TYPE_LEPUS_VALUE);
  tag1 = LEPUS_VALUE_GET_NORM_TAG(op1);
  tag2 = LEPUS_VALUE_GET_NORM_TAG(op2);
  /* fast path for float64 */
  if (tag1 == LEPUS_TAG_FLOAT64 && tag2 == LEPUS_TAG_FLOAT64) {
    double d1, d2;
    d1 = LEPUS_VALUE_GET_FLOAT64(op1);
    d2 = LEPUS_VALUE_GET_FLOAT64(op2);
    return __JS_NewFloat64(ctx, d1 + d2);
  }

  if (tag1 == LEPUS_TAG_OBJECT || tag2 == LEPUS_TAG_OBJECT) {
    op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE);
    if (LEPUS_IsException(op1)) {
      goto exception;
    }

    op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE);
    if (LEPUS_IsException(op2)) {
      goto exception;
    }
    tag1 = LEPUS_VALUE_GET_NORM_TAG(op1);
    tag2 = LEPUS_VALUE_GET_NORM_TAG(op2);
  }

  if (tag_is_string(tag1) || tag_is_string(tag2)) {
    return JS_ConcatString_GC(ctx, op1, op2);
  }

  op1 = JS_ToNumericFree(ctx, op1);
  if (LEPUS_IsException(op1)) {
    goto exception;
  }
  op2 = JS_ToNumericFree(ctx, op2);
  if (LEPUS_IsException(op2)) {
    goto exception;
  }
  tag1 = LEPUS_VALUE_GET_NORM_TAG(op1);
  tag2 = LEPUS_VALUE_GET_NORM_TAG(op2);

  if (tag1 == LEPUS_TAG_INT && tag2 == LEPUS_TAG_INT) {
    int32_t v1, v2;
    int64_t v;
    v1 = LEPUS_VALUE_GET_INT(op1);
    v2 = LEPUS_VALUE_GET_INT(op2);
    v = (int64_t)v1 + (int64_t)v2;
    return LEPUS_NewInt64(ctx, v);
  }
  if (tag1 == LEPUS_TAG_BIG_INT && tag2 == LEPUS_TAG_BIG_INT) {
    JSBigInt *p1, *p2, *r;
    JSBigIntBuf buf1, buf2;
    /* bigint result */
    p1 = (JSBigInt *)LEPUS_VALUE_GET_PTR(op1);
    p2 = (JSBigInt *)LEPUS_VALUE_GET_PTR(op2);
    r = js_bigint_add(ctx, p1, p2, 0);
    if (!r) goto exception;
    return JS_CompactBigInt(ctx, r);
  }
  /* float64 result */
  if (JS_ToFloat64Free(ctx, &d1, op1)) {
    goto exception;
  }
  if (JS_ToFloat64Free(ctx, &d2, op2)) goto exception;
  return __JS_NewFloat64(ctx, d1 + d2);
exception:
  return LEPUS_EXCEPTION;
}

no_inline LEPUSValue prim_js_not_slow_gc(LEPUSContext *ctx, LEPUSValue op) {
  int32_t v1;
  op = JS_ToNumericFree(ctx, op);
  HandleScope func_scope(ctx, &op, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_IsException(op)) return LEPUS_EXCEPTION;

  if (LEPUS_VALUE_IS_BIG_INT(op)) {
    JSBigInt *r;
    r = js_bigint_not(ctx, LEPUS_VALUE_GET_BIGINT(op));
    if (!r) return LEPUS_EXCEPTION;
    return JS_CompactBigInt(ctx, r);
  }
  if (unlikely(JS_ToInt32Free(ctx, &v1, op))) {
    return LEPUS_EXCEPTION;
  }
  return LEPUS_NewInt32(ctx, ~v1);
}

LEPUSValue prim_js_binary_arith_slow_gc(LEPUSContext *ctx, LEPUSValue op1,
                                        LEPUSValue op2, OPCodeEnum op) {
  int64_t tag1, tag2;
  double d1, d2;
  HandleScope func_scope(ctx, &op1, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&op2, HANDLE_TYPE_LEPUS_VALUE);
  tag1 = LEPUS_VALUE_GET_NORM_TAG(op1);
  tag2 = LEPUS_VALUE_GET_NORM_TAG(op2);
  /* fast path for float operations */
  if (tag1 == LEPUS_TAG_FLOAT64 && tag2 == LEPUS_TAG_FLOAT64) {
    d1 = LEPUS_VALUE_GET_FLOAT64(op1);
    d2 = LEPUS_VALUE_GET_FLOAT64(op2);
    goto handle_float64;
  }
  op1 = JS_ToNumericFree(ctx, op1);
  if (LEPUS_IsException(op1)) {
    goto exception;
  }
  op2 = JS_ToNumericFree(ctx, op2);
  if (LEPUS_IsException(op2)) {
    goto exception;
  }
  tag1 = LEPUS_VALUE_GET_NORM_TAG(op1);
  tag2 = LEPUS_VALUE_GET_NORM_TAG(op2);

  if (tag1 == LEPUS_TAG_INT && tag2 == LEPUS_TAG_INT) {
    int32_t v1, v2;
    int64_t v;
    v1 = LEPUS_VALUE_GET_INT(op1);
    v2 = LEPUS_VALUE_GET_INT(op2);
    switch (op) {
      case OP_sub:
        v = (int64_t)v1 - (int64_t)v2;
        break;
      case OP_mul:
        v = (int64_t)v1 * (int64_t)v2;
        if (v == 0 && (v1 | v2) < 0) {
          return __JS_NewFloat64(ctx, -0.0);
        }
        break;
      case OP_div:
        return LEPUS_NewFloat64(ctx, (double)v1 / (double)v2);
      case OP_mod:
        if (v1 < 0 || v2 <= 0) {
          return LEPUS_NewFloat64(ctx, fmod(v1, v2));
        } else {
          v = (int64_t)v1 % (int64_t)v2;
        }
        break;
      case OP_pow:
        return LEPUS_NewFloat64(ctx, js_pow(v1, v2));
      default:
        abort();
    }
    return LEPUS_NewInt64(ctx, v);
  } else if (tag1 == LEPUS_TAG_BIG_INT && tag2 == LEPUS_TAG_BIG_INT) {
    JSBigInt *p1, *p2, *r;
    JSBigIntBuf buf1, buf2;
    /* bigint result */

    p1 = (JSBigInt *)LEPUS_VALUE_GET_PTR(op1);
    p2 = (JSBigInt *)LEPUS_VALUE_GET_PTR(op2);
    switch (op) {
      case OP_add:
        r = js_bigint_add(ctx, p1, p2, 0);
        break;
      case OP_sub:
        r = js_bigint_add(ctx, p1, p2, 1);
        break;
      case OP_mul:
        r = js_bigint_mul(ctx, p1, p2);
        break;
      case OP_div:
        r = js_bigint_divrem(ctx, p1, p2, FALSE);
        break;
      case OP_mod:
        r = js_bigint_divrem(ctx, p1, p2, TRUE);
        break;
      case OP_pow:
        r = js_bigint_pow(ctx, p1, p2);
        break;
      default:
        abort();
    }
    if (!r) goto exception;
    return JS_CompactBigInt(ctx, r);
  } else {
    double dr;
    /* float64 result */
    if (JS_ToFloat64Free(ctx, &d1, op1)) {
      goto exception;
    }
    if (JS_ToFloat64Free(ctx, &d2, op2)) goto exception;
  handle_float64:
    switch (op) {
      case OP_sub:
        dr = d1 - d2;
        break;
      case OP_mul:
        dr = d1 * d2;
        break;
      case OP_div:
        dr = d1 / d2;
        break;
      case OP_mod:
        dr = fmod(d1, d2);
        break;
      case OP_pow:
        dr = js_pow(d1, d2);
        break;
      default:
        abort();
    }
    return __JS_NewFloat64(ctx, dr);
  }
exception:
  return LEPUS_EXCEPTION;
}

double prim_js_fmod_double_gc(double a, double b) {
  double c = fmod(a, b);
  return c;
}

LEPUSValue prim_js_binary_logic_slow_gc(LEPUSContext *ctx, LEPUSValue op1,
                                        LEPUSValue op2, OPCodeEnum op) {
  uint32_t v1, v2, r;
  int64_t tag1, tag2;
  HandleScope func_scope(ctx, &op1, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&op2, HANDLE_TYPE_LEPUS_VALUE);
  op1 = JS_ToNumericFree(ctx, op1);
  if (LEPUS_IsException(op1)) goto exception;
  op2 = JS_ToNumericFree(ctx, op2);
  if (LEPUS_IsException(op2)) goto exception;
  tag1 = LEPUS_VALUE_GET_TAG(op1);
  tag2 = LEPUS_VALUE_GET_TAG(op2);

  if (tag1 == LEPUS_TAG_BIG_INT && tag2 == LEPUS_TAG_BIG_INT) {
    JSBigInt *p1, *p2, *r;
    JSBigIntBuf buf1, buf2;
    p1 = LEPUS_VALUE_GET_BIGINT(op1);
    p2 = LEPUS_VALUE_GET_BIGINT(op2);
    switch (op) {
      case OP_and:
      case OP_or:
      case OP_xor:
        r = js_bigint_logic(ctx, p1, p2, op);
        break;
      case OP_shl:
      case OP_sar: {
        js_slimb_t shift;
        shift = js_bigint_get_si_sat(p2);
        if (shift > INT32_MAX)
          shift = INT32_MAX;
        else if (shift < -INT32_MAX)
          shift = -INT32_MAX;
        if (op == OP_sar) shift = -shift;
        if (shift >= 0)
          r = js_bigint_shl(ctx, p1, shift);
        else
          r = js_bigint_shr(ctx, p1, -shift);
      } break;
      default:
        abort();
    }
    if (!r) goto exception;
    return JS_CompactBigInt(ctx, r);
  }

  if (unlikely(JS_ToInt32Free(ctx, reinterpret_cast<int32_t *>(&v1), op1))) {
    goto exception;
  }
  if (unlikely(JS_ToInt32Free(ctx, reinterpret_cast<int32_t *>(&v2), op2)))
    goto exception;
  switch (op) {
    case OP_shl:
      r = v1 << (v2 & 0x1f);
      break;
    case OP_sar:
      r = static_cast<int>(v1) >> (v2 & 0x1f);
      break;
    case OP_and:
      r = v1 & v2;
      break;
    case OP_or:
      r = v1 | v2;
      break;
    case OP_xor:
      r = v1 ^ v2;
      break;
    default:
      prim_abort();
  }
  return LEPUS_NewInt32(ctx, r);
exception:
  return LEPUS_EXCEPTION;
}

LEPUSValue prim_js_shr_slow_gc(LEPUSContext *ctx, LEPUSValue op1,
                               LEPUSValue op2) {
  uint32_t v1, v2, r;
  HandleScope func_scope(ctx, &op1, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&op2, HANDLE_TYPE_LEPUS_VALUE);
  op1 = JS_ToNumericFree(ctx, op1);
  if (LEPUS_IsException(op1)) {
    goto exception;
  }
  op2 = JS_ToNumericFree(ctx, op2);
  if (LEPUS_IsException(op2)) {
    goto exception;
  }
  if (LEPUS_VALUE_IS_BIG_INT(op1) || LEPUS_VALUE_IS_BIG_INT(op2)) {
    LEPUS_ThrowTypeError(ctx, "bigint operands are forbidden for >>>");
    return LEPUS_EXCEPTION;
  }
  if (unlikely(JS_ToUint32Free(ctx, &v1, op1))) {
    goto exception;
  }
  if (unlikely(JS_ToUint32Free(ctx, &v2, op2))) goto exception;
  r = v1 >> (v2 & 0x1f);
  return JS_NewUint32(ctx, r);
exception:
  return LEPUS_EXCEPTION;
}

LEPUSValue prim_js_relation_slow_gc(LEPUSContext *ctx, LEPUSValue op1,
                                    LEPUSValue op2, OPCodeEnum op) {
  int res;
  int64_t tag1, tag2;
  HandleScope func_scope(ctx, &op1, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&op2, HANDLE_TYPE_LEPUS_VALUE);

  op1 = JS_ToPrimitiveFree_GC(ctx, op1, HINT_NUMBER);
  if (LEPUS_IsException(op1)) {
    goto exception;
  }
  op2 = JS_ToPrimitiveFree_GC(ctx, op2, HINT_NUMBER);
  if (LEPUS_IsException(op2)) {
    ;
    goto exception;
  }

  if (JS_IsSeparableString(op1)) {
    auto tmp = JS_GetSeparableStringContent_GC(ctx, op1);
    op1 = tmp;
  }

  if (JS_IsSeparableString(op2)) {
    auto tmp = JS_GetSeparableStringContent_GC(ctx, op2);
    op2 = tmp;
  }

  tag1 = LEPUS_VALUE_GET_NORM_TAG(op1);
  tag2 = LEPUS_VALUE_GET_NORM_TAG(op2);

  if (tag1 == LEPUS_TAG_STRING && tag2 == LEPUS_TAG_STRING) {
    JSString *p1, *p2;
    p1 = LEPUS_VALUE_GET_STRING(op1);
    p2 = LEPUS_VALUE_GET_STRING(op2);
    res = js_string_compare(ctx, p1, p2);
    switch (op) {
      case OP_lt:
        res = (res < 0);
        break;
      case OP_lte:
        res = (res <= 0);
        break;
      case OP_gt:
        res = (res > 0);
        break;
      default:
      case OP_gte:
        res = (res >= 0);
        break;
    }
  } else if ((tag1 == LEPUS_TAG_NULL || tag1 == LEPUS_TAG_BOOL ||
              tag1 == LEPUS_TAG_INT || tag1 == LEPUS_TAG_FLOAT64) &&
             (tag2 == LEPUS_TAG_NULL || tag2 == LEPUS_TAG_BOOL ||
              tag2 == LEPUS_TAG_INT || tag2 == LEPUS_TAG_FLOAT64)) {
    goto float64_compare;
  } else {
    if ((((tag1 == LEPUS_TAG_BIG_INT) && tag_is_string(tag2)) ||
         ((tag2 == LEPUS_TAG_BIG_INT) && tag_is_string(tag1)))) {
      if (tag_is_string(tag1)) {
        op1 = JS_StringToBigInt(ctx, op1);
        if (LEPUS_VALUE_GET_TAG(op1) != LEPUS_TAG_BIG_INT)
          goto invalid_bigint_string;
      }
      if (tag_is_string(tag2)) {
        op2 = JS_StringToBigInt(ctx, op2);
        if (LEPUS_VALUE_GET_TAG(op2) != LEPUS_TAG_BIG_INT) {
        invalid_bigint_string:
          res = FALSE;
          goto done;
        }
      }
    } else {
      op1 = JS_ToNumericFree(ctx, op1);
      if (LEPUS_IsException(op1)) {
        goto exception;
      }
      op2 = JS_ToNumericFree(ctx, op2);
      if (LEPUS_IsException(op2)) {
        goto exception;
      }
    }

    tag1 = LEPUS_VALUE_GET_NORM_TAG(op1);
    tag2 = LEPUS_VALUE_GET_NORM_TAG(op2);

    if (tag1 == LEPUS_TAG_BIG_INT || tag2 == LEPUS_TAG_BIG_INT) {
      res = js_compare_bigint(ctx, op, op1, op2);
    } else {
      double d1, d2;

    float64_compare:
      /* can use floating point comparison */
      if (tag1 == LEPUS_TAG_FLOAT64) {
        d1 = LEPUS_VALUE_GET_FLOAT64(op1);
      } else if (tag1 == LEPUS_TAG_INT) {
        d1 = LEPUS_VALUE_GET_INT(op1);
      } else if (tag1 == LEPUS_TAG_BOOL) {
        d1 = LEPUS_VALUE_GET_BOOL(op1) ? 1 : 0;
      } else {
        d1 = 0;
      }
      if (tag2 == LEPUS_TAG_FLOAT64) {
        d2 = LEPUS_VALUE_GET_FLOAT64(op2);
      } else if (tag2 == LEPUS_TAG_INT) {
        d2 = LEPUS_VALUE_GET_INT(op2);
      } else if (tag2 == LEPUS_TAG_BOOL) {
        d2 = LEPUS_VALUE_GET_BOOL(op2) ? 1 : 0;
      } else {
        d2 = 0;
      }
      switch (op) {
        case OP_lt:
          res = (d1 < d2); /* if NaN return false */
          break;
        case OP_lte:
          res = (d1 <= d2); /* if NaN return false */
          break;
        case OP_gt:
          res = (d1 > d2); /* if NaN return false */
          break;
        default:
        case OP_gte:
          res = (d1 >= d2); /* if NaN return false */
          break;
      }
    }
  }
done:
  return LEPUS_NewBool(ctx, res);
exception:
  return LEPUS_EXCEPTION;
  ;
}

LEPUSValue prim_js_eq_slow_gc(LEPUSContext *ctx, LEPUSValue op1, LEPUSValue op2,
                              int is_neq) {
  int res;
  int64_t tag1, tag2;
  HandleScope func_scope(ctx, &op1, HANDLE_TYPE_LEPUS_VALUE);
  func_scope.PushHandle(&op2, HANDLE_TYPE_LEPUS_VALUE);
redo:
  tag1 = LEPUS_VALUE_GET_NORM_TAG(op1);
  tag2 = LEPUS_VALUE_GET_NORM_TAG(op2);
  if (tag_is_number(tag1) && tag_is_number(tag2)) {
    if (tag1 == LEPUS_TAG_INT && tag2 == LEPUS_TAG_INT) {
      res = LEPUS_VALUE_GET_INT(op1) == LEPUS_VALUE_GET_INT(op2);
    } else if ((tag1 == LEPUS_TAG_FLOAT64 &&
                (tag2 == LEPUS_TAG_INT || tag2 == LEPUS_TAG_FLOAT64)) ||
               (tag2 == LEPUS_TAG_FLOAT64 &&
                (tag1 == LEPUS_TAG_INT || tag1 == LEPUS_TAG_FLOAT64))) {
      double d1, d2;
      if (tag1 == LEPUS_TAG_FLOAT64) {
        d1 = LEPUS_VALUE_GET_FLOAT64(op1);
      } else {
        d1 = LEPUS_VALUE_GET_INT(op1);
      }
      if (tag2 == LEPUS_TAG_FLOAT64) {
        d2 = LEPUS_VALUE_GET_FLOAT64(op2);
      } else {
        d2 = LEPUS_VALUE_GET_INT(op2);
      }
      res = (d1 == d2);
    } else {
      res = js_compare_bigint(ctx, OP_eq, op1, op2);
    }
  } else if (tag1 == tag2) {
    res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT);
  } else if ((tag1 == LEPUS_TAG_NULL && tag2 == LEPUS_TAG_UNDEFINED) ||
             (tag2 == LEPUS_TAG_NULL && tag1 == LEPUS_TAG_UNDEFINED)) {
    res = TRUE;
  } else if (tag_is_string(tag1) && tag_is_string(tag2)) {
    /* needed when comparing strings and ropes */
    res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT);
  } else if ((tag_is_string(tag1) && tag_is_number(tag2)) ||
             (tag_is_string(tag2) && tag_is_number(tag1))) {
    if (tag1 == LEPUS_TAG_BIG_INT || tag2 == LEPUS_TAG_BIG_INT) {
      if (tag_is_string(tag1)) {
        op1 = JS_StringToBigInt(ctx, op1);
        if (LEPUS_VALUE_GET_TAG(op1) != LEPUS_TAG_BIG_INT)
          goto invalid_bigint_string;
      }
      if (tag_is_string(tag2)) {
        op2 = JS_StringToBigInt(ctx, op2);
        if (LEPUS_VALUE_GET_TAG(op2) != LEPUS_TAG_BIG_INT) {
        invalid_bigint_string:
          res = FALSE;
          goto done;
        }
      }
    } else {
      op1 = JS_ToNumericFree(ctx, op1);
      if (LEPUS_IsException(op1)) {
        goto exception;
      }
      op2 = JS_ToNumericFree(ctx, op2);
      if (LEPUS_IsException(op2)) {
        goto exception;
      }
    }
    res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT);
  } else if (tag1 == LEPUS_TAG_BOOL) {
    op1 = LEPUS_NewInt32(ctx, LEPUS_VALUE_GET_BOOL(op1));
    goto redo;
  } else if (tag2 == LEPUS_TAG_BOOL) {
    op2 = LEPUS_NewInt32(ctx, LEPUS_VALUE_GET_BOOL(op2));
    goto redo;
  } else if ((tag1 == LEPUS_TAG_OBJECT &&
              (tag_is_number(tag2) || tag_is_string(tag2) ||
               tag2 == LEPUS_TAG_SYMBOL)) ||
             (tag2 == LEPUS_TAG_OBJECT &&
              (tag_is_number(tag1) || tag_is_string(tag1) ||
               tag1 == LEPUS_TAG_SYMBOL))) {
    op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE);
    if (LEPUS_IsException(op1)) {
      goto exception;
    }
    op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE);
    if (LEPUS_IsException(op2)) {
      goto exception;
    }
    goto redo;
  } else {
    res = FALSE;
  }
done:
  return LEPUS_NewBool(ctx, res ^ is_neq);
exception:
  return LEPUS_EXCEPTION;
}

LEPUSValue prim_js_strict_eq_slow_gc(LEPUSContext *ctx, LEPUSValue op1,
                                     LEPUSValue op2, BOOL is_neq) {
  BOOL res;
  res = js_strict_eq(ctx, op1, op2);
  return LEPUS_NewBool(ctx, res ^ is_neq);
}

LEPUSValue prim_js_operator_instanceof_gc(LEPUSContext *ctx, LEPUSValue op1,
                                          LEPUSValue op2) {
  BOOL ret;
  ret = JS_IsInstanceOf_GC(ctx, op1, op2);
  if (ret < 0) {
    return LEPUS_EXCEPTION;
  }
  return LEPUS_NewBool(ctx, ret);
}

__exception int js_post_inc_slow_gc(LEPUSContext *ctx, LEPUSValue *sp,
                                    OPCodeEnum op) {
  LEPUSValue op1 = LEPUS_UNDEFINED;

  /* XXX: allow custom operators */
  op1 = sp[-1];
  op1 = JS_ToNumericFree(ctx, op1);
  HandleScope func_scope(ctx, &op1, HANDLE_TYPE_LEPUS_VALUE);
  if (LEPUS_IsException(op1)) {
    sp[-1] = LEPUS_UNDEFINED;
    return -1;
  }
  sp[-1] = op1;
  sp[0] = op1;
  sp[0] = prim_js_unary_arith_slow_gc(
      ctx, sp[0], static_cast<OPCodeEnum>(op - OP_post_dec + OP_dec));
  if (LEPUS_IsException(sp[0])) return -1;
  return 0;
}

LEPUSValue prim_js_operator_in_gc(LEPUSContext *ctx, LEPUSValue op1,
                                  LEPUSValue op2) {
  HandleScope func_scope(ctx);
  JSAtom atom;
  int ret;

  if (LEPUS_VALUE_IS_NOT_OBJECT(op2) && !LEPUS_VALUE_IS_LEPUS_REF(op2)) {
    atom = js_value_to_atom(ctx, op1);
    if (unlikely(atom == JS_ATOM_NULL)) {
      LEPUS_ThrowTypeError(ctx, "invalid 'in' operand search for null");
      return LEPUS_EXCEPTION;
    }
    func_scope.PushLEPUSAtom(atom);
    const char *msg = JS_AtomToCString_GC(ctx, atom);
    char buffer[200];
    if (msg) {
      snprintf(buffer, 199, "invalid 'in' operand search for '%s'", msg);
    } else {
      snprintf(buffer, 199, "invalid 'in' operand search for null");
    }
    LEPUS_ThrowTypeError(ctx, "%s", buffer);
    return LEPUS_EXCEPTION;
  }
  atom = LEPUS_ValueToAtom(ctx, op1);
  if (unlikely(atom == JS_ATOM_NULL)) {
    return LEPUS_EXCEPTION;
  }
  func_scope.PushLEPUSAtom(atom);
  ret = LEPUS_HasProperty(ctx, op2, atom);
  if (ret < 0) {
    return LEPUS_EXCEPTION;
  }
  return LEPUS_NewBool(ctx, ret);
}

LEPUSValue prim_js_operator_delete_gc(LEPUSContext *ctx, LEPUSValue op1,
                                      LEPUSValue op2) {
  JSAtom atom = LEPUS_ValueToAtom(ctx, op2);
  if (unlikely(atom == JS_ATOM_NULL)) {
    return LEPUS_EXCEPTION;
  }
  HandleScope func_scope(ctx);
  func_scope.PushLEPUSAtom(atom);
  int ret = LEPUS_DeleteProperty(ctx, op1, atom, LEPUS_PROP_THROW_STRICT);
  if (unlikely(ret < 0)) {
    return LEPUS_EXCEPTION;
  }
  return LEPUS_NewBool(ctx, ret);
}
#endif

#ifdef ENABLE_PRIMJS_SNAPSHOT
extern "C" void _call_stub_entry();

extern "C" void _dispatch_table();

extern "C" void _dispatch_table_offset();

typedef unsigned char u_char;
typedef u_char *address;

address _table_gc[NUM_OF_TOS_STATES][OP_COUNT];

#define CAST_TO_FN_PTR(func_type, value) (reinterpret_cast<func_type>(value))

static void initialize_dispatchTable(LEPUSContext *ctx) {
  for (int i = 0; i < NUM_OF_TOS_STATES; i++) {
    for (int j = 1; j < OP_COUNT; j++) {
      int offset = i * (OP_COUNT - 1) + j - 1;
      int *table_entry =
          reinterpret_cast<int *>(&_dispatch_table_offset) + offset;
      int table_offset = *table_entry;
      _table_gc[i][j] = (address)&_dispatch_table + table_offset;
    }
  }
  ctx->dispatch_table = _table_gc;
}

static QuickJsCallStub call_stub() {
  return CAST_TO_FN_PTR(QuickJsCallStub, &_call_stub_entry);
}

void PrimInit_GC(LEPUSContext *ctx) {
  initialize_dispatchTable(ctx);
#ifdef ENABLE_PRIMJS_SNAPSHOT
  entry = call_stub();
#endif
}

#endif

// <primjs end>

#pragma clang diagnostic pop

__attribute__((always_inline)) HandleScope::HandleScope(LEPUSRuntime *rt)
    : ptr_handles(rt->ptr_handles),
      handle_prev_idx(ptr_handles->GetHeapObjIdx()) {}

__attribute__((always_inline)) HandleScope::HandleScope(LEPUSContext *ctx)
    : ptr_handles(ctx->rt->ptr_handles),
      handle_prev_idx(ptr_handles->GetHeapObjIdx()) {}

HandleScope::HandleScope(LEPUSContext *ctx, void *ptr, HandleType type)
    : ptr_handles(ctx->rt->ptr_handles),
      handle_prev_idx(ptr_handles->GetHeapObjIdx()) {
  ptr_handles->PushHandle(ptr, type);
}

HandleScope::~HandleScope() { ptr_handles->SetHeapObjIdx(handle_prev_idx); }

void HandleScope::PushHandle(void *ptr, HandleType type) {
  ptr_handles->PushHandle(ptr, type);
}
void HandleScope::PushLEPUSAtom(JSAtom atom) {
  ptr_handles->PushLEPUSAtom(atom);
}

void HandleScope::PushLEPUSValueArrayHandle(LEPUSValue *array, int size,
                                            bool need_init) {
  ptr_handles->PushLEPUSValueArrayHandle(array, size, need_init);
}
void HandleScope::ResetHandle(void *ptr, HandleType type) {
  ptr_handles->ResetHandle(ptr, type);
}
void HandleScope::PushLEPUSPropertyDescriptor(LEPUSPropertyDescriptor *desc) {
  ptr_handles->PushLEPUSPropertyDescriptor(desc);
}

void LEPUS_PushHandle(LEPUSContext *ctx, void *ptr, int type) {
  if (type == 1) {
    ctx->ptr_handles->PushHandle(ptr, HANDLE_TYPE_LEPUS_VALUE);
  } else if (type == 2) {
    ctx->ptr_handles->PushHandle(ptr, HANDLE_TYPE_HEAP_OBJ);
  }
}
void LEPUS_ResetHandle(LEPUSContext *ctx, void *ptr, int type) {
  if (type == 1) {
    ctx->ptr_handles->ResetHandle(ptr, HANDLE_TYPE_LEPUS_VALUE);
  } else if (type == 2) {
    ctx->ptr_handles->ResetHandle(ptr, HANDLE_TYPE_HEAP_OBJ);
  }
}

// public
/* trace gc begin */

void *GetNapiScope_GC(LEPUSContext *ctx) {
  return reinterpret_cast<void *>(ctx->napi_scope);
}

void SetNapiScope_GC(LEPUSContext *ctx, void *scope) {
  ctx->napi_scope = reinterpret_cast<NAPIHandleScope *>(scope);
}
void InitNapiScope_GC(LEPUSContext *ctx) {
  ctx->napi_scope = new NAPIHandleScope(ctx);
}
void FreeNapiScope_GC(LEPUSContext *ctx) {
  if (ctx->napi_scope) {
    delete ctx->napi_scope;
    ctx->napi_scope = nullptr;
  }
}

void JS_VisitLEPUSValue_GC(LEPUSRuntime *rt, LEPUSValue *val, int local_idx) {
  rt->gc->GetVisitor()->VisitRootLEPUSValue(val, local_idx);
}

void LEPUS_TrigGC(LEPUSRuntime *rt) {
  // rt->malloc_state.allocate_state.open_madvise = true;
  rt->gc->CollectGarbage();
  // rt->malloc_state.allocate_state.open_madvise = false;
}

void AddCurNode(LEPUSRuntime *rt, void *node, int type) {
#ifdef ENABLE_GC_DEBUG_TOOLS
  add_cur_node(rt, node, type);
#endif
}

void DeleteCurNode(LEPUSRuntime *rt, void *node, int type) {
#ifdef ENABLE_GC_DEBUG_TOOLS
  delete_cur_node(rt, node, type);
#endif
}

bool CheckValidNode(LEPUSRuntime *rt, void *node, int type) {
#ifdef ENABLE_GC_DEBUG_TOOLS
  return (get_cur_node_cnt(rt, node, type) != 0);
#endif
  return true;
}

__attribute__((unused)) bool CheckValidPtr_GC(void *runtime, void *ptr) {
#ifdef ENABLE_GC_DEBUG_TOOLS
  return (get_cur_cnt(runtime, ptr) != 0);
#else
  return true;
#endif
}

void DisposeGlobal_GC(LEPUSRuntime *runtime, LEPUSValue *global_handle) {
#ifdef ENABLE_GC_DEBUG_TOOLS
  DCHECK(CheckValidNode(runtime, reinterpret_cast<void *>(global_handle), 1));
  DeleteCurNode(runtime, reinterpret_cast<void *>(global_handle), 1);
#endif
  runtime->global_handles_->Destroy(global_handle);
}

LEPUSValue *GlobalizeReference_GC(LEPUSRuntime *runtime, LEPUSValue val,
                                  bool is_weak) {
  return runtime->global_handles_->Create(val, is_weak);
}

void SetGlobalWeak_GC(LEPUSRuntime *runtime, LEPUSValue *global_handle,
                      void *data, void (*cb)(void *)) {
  runtime->global_handles_->SetWeak(global_handle, data, cb);
}

void ClearGlobalWeak_GC(LEPUSRuntime *runtime, LEPUSValue *global_handle) {
  runtime->global_handles_->ClearWeak(global_handle);
}

void SetWeakState_GC(LEPUSRuntime *runtime, LEPUSValue *global_handle) {
  runtime->global_handles_->SetWeakState(global_handle);
}

bool gc_info_enabled() {
#ifdef ENABLE_COMPATIBLE_MM
  return GC_INFO_ENABLE & settingsFlag;
#else
  return false;
#endif
}

bool lepusng_stragety_enabled() {
#ifdef ENABLE_COMPATIBLE_MM
  return ENABLE_LEPUSNG_STRAGETY & settingsFlag;
#else
  return false;
#endif
}

/* trace gc end */

void GarbageCollector::CollectGarbage(size_t size, bool gc_state) noexcept {
  TRACE_EVENT("RunGC");
  rt_->gc_cnt++;
#ifdef ENABLE_FORCE_GC
  if (rt_->gc_cnt % 1000 == 0) {
#if defined(ANDROID) || defined(__ANDROID__)
    __android_log_print(ANDROID_LOG_ERROR, "PRIMJS_GC",
                        "----------------> trace_gc_cnt: %zu, rt: %p\n",
                        rt_->gc_cnt, rt_);
#else
    std::cout << "----------------> trace_gc_cnt: " << rt_->gc_cnt
              << " rt: " << rt_ << std::endl;
#endif
  }
#endif
  if (forbid_gc_ > 0) {
    size_t expand_size = size * 1.5;
    if (expand_size < 5 * MB) {
      expand_size = 5 * MB;
    }
    rt_->malloc_state.allocate_state.footprint_limit += expand_size;
    return;
  }
  if (rt_->workerThreadPool == nullptr) {
    rt_->gc->Init(rt_);
    rt_->workerThreadPool = new ByteThreadPool("gc", CREATE_THREAD_NUM, -1);
    rt_->malloc_state.allocate_state.pool =
        static_cast<void *>(rt_->workerThreadPool);
  }
  forbid_gc_++;
  rt_->mf.lepus_malloc = js_def_allocate_gc;
  rt_->mf.lepus_realloc = js_def_reallocate_gc;

  size_t heapsize_before = rt_->malloc_state.allocate_state.footprint;

  int64_t gc_begin = get_daytime();
  // 1. mark phase
  MarkLiveObjects();
#ifdef ENABLE_TRACING_GC_LOG
  int64_t mark_end = get_daytime();
#endif
  // 2. sweep phase
  SweepDeadObjects();
#ifdef ENABLE_TRACING_GC_LOG
  PrintGCLog(gc_begin, mark_end);
#endif  // ENABLE_TRACING_GC_LOG
  UpdateFootprintLimit(size);

  rt_->mf.lepus_malloc = js_def_allocate;
  rt_->mf.lepus_realloc = js_def_reallocate;
#ifndef ENABLE_FORCE_GC
  if (rt_->mem_for_oom == nullptr) {
    rt_->malloc_state.allocate_state.footprint_limit += 64 * KB;
    rt_->mem_for_oom = lepus_malloc_rt(rt_, 16 * KB, ALLOC_TAG_WITHOUT_PTR);
  }
#endif
  forbid_gc_--;
  int64_t gc_end = get_daytime();
  AddGCDuration(gc_end - gc_begin);
  if (gc_info_enabled() && !gc_state) {
    UpdateGCInfo(heapsize_before, gc_end - gc_begin);
  }
}

void GarbageCollector::UpdateGCInfo(size_t heapsize_before, int64_t duration) {
  if (rt_->gc_cnt % UPDATE_GC_INFO_TIMES == 0) {
    gc_info << "{\n"
            << "  \"gc_info\": [\n";
  }
  pthread_mutex_lock(&runtime_mutex);
  std::unordered_set<LEPUSRuntime *> *g_rt_set = js_get_rt_set();
  size_t rt_num = g_rt_set->size();
  size_t total_mem = 0;
  for (auto it : *g_rt_set) {
    total_mem += it->malloc_state.allocate_state.footprint;
  }
  pthread_mutex_unlock(&runtime_mutex);
  gc_info << "    {\n"
          << "      \"cur_cnt\": " << rt_->gc_cnt << ",\n"
          << "      \"cur_duration\": " << duration / MS << ",\n"
          << "      \"heapsize_before\": " << heapsize_before / KB << ",\n"
          << "      \"heapsize_after\": "
          << rt_->malloc_state.allocate_state.footprint / KB << ",\n"
          << "      \"num_of_rt\": " << rt_num << ",\n"
          << "      \"survival_time\": "
          << (get_daytime() - rt_->init_time) / MS << ",\n"
          << "      \"timestamp\": " << get_daytime() << ",\n"
          << "      \"rt_ptr\": \"" << rt_ << "\",\n"
          << "      \"total_mem\": " << total_mem / KB << ",\n";
  if (rt_->rt_info) {
    gc_info << "      \"rt_info\": \"" << rt_->rt_info << "\"\n";
  }
  if ((rt_->gc_cnt + 1) % UPDATE_GC_INFO_TIMES == 0) {
    gc_info << "    }\n"
            << "  ]\n"
            << "}\n";
    info_size++;
    LEPUSContext *ctx = nullptr;
    struct list_head *el, *el1;
    list_for_each_safe(el, el1, &rt_->context_list) {
      ctx = list_entry(el, LEPUSContext, link);
    }
    std::string gc_info_str = gc_info.str();
    GCObserver *observer = static_cast<GCObserver *>(rt_->gc_observer);
    if (observer) {
      observer->OnGC(std::move(gc_info_str));
    }
    gc_info.str("");
    info_size = 0;
  } else {
    gc_info << "    },\n";
    info_size++;
  }
}

void GarbageCollector::MarkLiveObjects() noexcept { visitor->ScanRoots(); }

void GarbageCollector::SweepDeadObjects() noexcept {
  sweeper->sweep_finalizer();
  visitor->VisitObjectDuringGC();
  sweeper->sweep_free();
}

void GarbageCollector::DoOnlyFinalizer() noexcept {
  sweeper->traverse_chunk_for_finalizer(true);
}

#ifdef ENABLE_TRACING_GC_LOG
void GarbageCollector::PrintGCLog(int64_t mark_begin,
                                  int64_t mark_end) noexcept {
  int64_t sweep_end = get_daytime();
  int64_t internal_time = (mark_begin - last_gc_time) / 1000;
  mstate s = &rt_->malloc_state.allocate_state;
  double speed = (s->malloc_size_before_gc - s->malloc_size_after_gc) / 1024 /
                 internal_time;
  std::stringstream buf1;
  const char *info = "";
  if (rt_->rt_info != nullptr) {
    info = rt_->rt_info;
  }
  buf1 << "<--------- gc_info, gc_cnt: " << rt_->gc_cnt << " rt_info: " << info
       << " time: " << (mark_begin - gc_begin_time) / 1000
       << " ms --------->\nduration: " << (sweep_end - mark_begin) / 1000
       << " ms, mark: " << (mark_end - mark_begin) / 1000
       << " ms, sweep: " << (sweep_end - mark_end) / 1000 << " ms\n"
       << "sweep-finalizer: " << s->finalizer_time
       << " ms;\nsweep-free: " << s->free_time
       << " ms; sweep-free-set_bit: " << s->free_set_bit_time
       << " ms, sweep-free-gene_freelist: " << s->free_gene_freelist_time
       << " ms, sweep-free-mmap_chunk: " << s->free_mmap_chunk_time
       << " ms;\nsweep-release: " << s->release_time
       << "ms, release_seg_num: " << s->release_seg_num
       << ";\nmalloc_size_before: " << s->malloc_size_before_gc / 1024 << " KB "
       << "malloc_size_after: " << s->malloc_size_after_gc / 1024 << " KB "
       << "malloc_speed: " << speed << " KB/ms"
       << "; footprint_before: " << s->footprint_before_gc / 1024 << " KB "
       << "footprint_after: " << s->footprint / 1024 << " KB;\n"
       << "footprint_limit before: " << s->footprint_limit / 1024 << " KB;"
       << std::endl;
  last_gc_time = sweep_end;
#if defined(ANDROID) || defined(__ANDROID__)
  __android_log_print(ANDROID_LOG_ERROR, "PRIMJS_GC", " %s",
                      buf1.str().c_str());
#else
  std::cout << buf1.str().c_str() << "\n";
#endif
}
#endif

void GarbageCollector::SetMaxLimit(size_t limit) { max_limit = limit; }

size_t GarbageCollector::GetMaxLimit() {
  size_t cur_limit = (size_t)2048 * MB;
  if (max_limit != 0 && max_limit < cur_limit) {
    return max_limit;
  } else {
    return cur_limit;
  }
}

void GarbageCollector::UpdateFootprintLimit(size_t size) noexcept {
  if (rt_->is_lepusng && lepusng_stragety_enabled()) {
    return UpdateNGFootprintLimit(size);
  }
  size_t limit = GetMaxLimit();
  size_t new_limit;
  mstate s = &rt_->malloc_state.allocate_state;
#if defined(ANDROID) || defined(__ANDROID__) || defined(OS_IOS)
  if (s->footprint > 100 * MB) {
    size_t ss = s->footprint > s->cur_malloc_size * 3 ? s->footprint
                                                      : s->cur_malloc_size * 3;
    size_t expand_size = size * 1.5;
    expand_size = (expand_size < 5 * MB) ? 5 * MB : expand_size;
    ss = ss > s->footprint_limit + expand_size
             ? ss
             : s->footprint_limit + expand_size;
    new_limit = ss >= limit ? limit : ss;
  } else if (s->footprint > 50 * MB) {
    new_limit = s->footprint_limit >= limit ? limit : s->footprint * 1.2;
  } else if (s->footprint > 30 * MB) {
    new_limit = s->footprint_limit >= limit ? limit : s->footprint * 1.5;
  } else {
    new_limit = s->footprint_limit >= limit ? limit : s->footprint * 2;
  }
#else
  double times = (s->footprint_limit < 512 * MB) ? 4
                 : (s->footprint_limit < limit)  ? 2
                                                 : 1.5;
  new_limit = s->footprint_limit * times;
  new_limit = (new_limit > limit) ? limit : new_limit;
#endif
  s->footprint_limit = new_limit;
  if (s->footprint > 100 * MB &&
      (s->footprint_before_gc - s->footprint < (10 * MB)) && size > 100 * KB) {
    size_t ss = s->footprint_limit + 32 * MB;
    s->footprint_limit = ss >= limit ? limit : ss;
  }
}

void GarbageCollector::UpdateNGFootprintLimit(size_t size) noexcept {
  size_t limit = GetMaxLimit();
  size_t new_limit;
  mstate s = &rt_->malloc_state.allocate_state;
#if defined(ANDROID) || defined(__ANDROID__) || defined(OS_IOS)
  if (s->footprint > 100 * MB) {
    size_t ss = s->footprint > s->cur_malloc_size * 2 ? s->footprint
                                                      : s->cur_malloc_size * 2;
    new_limit = ss >= limit ? limit : ss;
  } else if (s->footprint > 50 * MB) {
    size_t ss = s->footprint > s->cur_malloc_size * 3 ? s->footprint
                                                      : s->cur_malloc_size * 3;
    new_limit = ss >= limit ? limit : ss;
  } else {
    size_t ss = s->footprint > s->cur_malloc_size * 2 ? s->footprint
                                                      : s->cur_malloc_size * 2;
    new_limit = ss >= limit ? limit : ss;
  }
#else
  double times = (s->footprint_limit < 512 * MB) ? 4
                 : (s->footprint_limit < limit)  ? 2
                                                 : 1.5;
  new_limit = s->footprint_limit * times;
  new_limit = (new_limit > limit) ? limit : new_limit;
#endif
  s->footprint_limit = new_limit;
  if (s->footprint > 100 * MB &&
      (s->footprint_before_gc - s->footprint < (10 * MB)) && size > 100 * KB) {
    size_t ss = s->footprint_limit + 32 * MB;
    s->footprint_limit = ss >= limit ? limit : ss;
  }
}

void Visitor::ScanRoots() {
  MlockScope scope(queue);

  ByteThreadPool *workerThreadPool = rt_->workerThreadPool;
  workerThreadPool->AddTask(
      new ByteLambdaTask([this](size_t) { ScanStack(); }));
  workerThreadPool->Start();
  workerThreadPool->AddTask(
      new ByteLambdaTask([this](size_t) { ScanHandles(); }));
  // scan context
  struct list_head *el, *el1;
  list_for_each_safe(el, el1, &rt_->context_list) {
    LEPUSContext *ctx = list_entry(el, LEPUSContext, link);
    workerThreadPool->AddTask(
        new ByteLambdaTask([this, ctx](size_t) { ScanContext(ctx); }));
  }
  workerThreadPool->AddTask(
      new ByteLambdaTask([this](size_t) { ScanRuntime(); }));

  bool addToExecute = (THREAD_NUM == CREATE_THREAD_NUM) ? false : true;
  workerThreadPool->WaitFinish(addToExecute);
}

void Visitor::ScanStack() noexcept {
  mstate m = &rt_->malloc_state.allocate_state;
  int local_idx = atomic_acqurie_local_idx(m);
  while (local_idx == -1) {
#ifndef _WIN32
    sched_yield();
#endif
    local_idx = atomic_acqurie_local_idx(m);
  }
  LEPUSStackFrame *sf = rt_->current_stack_frame;
  struct list_head *el;
  while (sf) {
    // arg_buf
    if (sf->arg_buf) {
      for (int i = 0; i < sf->arg_count; i++) {
        VisitRootLEPUSValue(sf->arg_buf[i], local_idx);
      }
    }
    // var_buf
    if (sf->var_buf) {
      LEPUSValue *cur_sp =
          sf->cur_sp ? sf->cur_sp : (sf->sp ? sf->sp : nullptr);
      if (cur_sp) {
        for (LEPUSValue *sp = sf->var_buf; sp < cur_sp; sp++) {
          VisitRootLEPUSValue(sp, local_idx);
        }
      }
    }
    VisitRootLEPUSValue(sf->cur_func, local_idx);
    list_for_each(el, &sf->var_ref_list) {
      JSVarRef *var_ref = list_entry(el, JSVarRef, link);
      VisitRootHeapObj(var_ref, local_idx);
    }

    if (sf->var_refs) {
      VisitRootHeapObj(sf->var_refs, local_idx);
    }
    sf = sf->prev_frame;
  }
  atomic_release_local_idx(m, local_idx);
}

void Visitor::ScanRuntime() noexcept {
  mstate m = &rt_->malloc_state.allocate_state;
  int local_idx = atomic_acqurie_local_idx(m);
  while (local_idx == -1) {
#ifndef _WIN32
    sched_yield();
#endif
    local_idx = atomic_acqurie_local_idx(m);
  }

  if (rt_->mem_for_oom) {
    VisitRootHeapObj(rt_->mem_for_oom, local_idx);
  }

  struct list_head *el, *el1;
  int i;
  // job_list
  list_for_each_safe(el, el1, &rt_->job_list) {
    JSJobEntry *e = list_entry(el, JSJobEntry, link);
    for (i = 0; i < e->argc; i++) {
      VisitRootLEPUSValue(e->argv[i], local_idx);
    }
    VisitRootHeapObj(e, local_idx);
  }
  // unhandled_rejections
  list_for_each_safe(el, el1, &rt_->unhandled_rejections) {
    JSUnhandledRejectionEntry *e =
        list_entry(el, JSUnhandledRejectionEntry, link);
    VisitRootLEPUSValue(e->error, local_idx);
    VisitRootHeapObj(e, local_idx);
  }
  // class_array
  for (i = 0; i < rt_->class_count; i++) {
    LEPUSClass *cl = &rt_->class_array[i];
    if (cl->class_id != 0) {
      PushObjAtom(cl->class_name, local_idx);
    }
  }
  if (rt_->class_array) VisitRootHeapObj(rt_->class_array, local_idx);

  // current_exception
  VisitRootLEPUSValue(rt_->current_exception, local_idx);

  // the atoms, will cause string leak
  // mark non-const atom
  for (i = 0; i < rt_->atom_size; i++) {
    JSAtomStruct *p = rt_->atom_array[i];
    if (!atom_is_free(p)) {
      VisitRootHeapObj(p, local_idx);
    }
  }
  if (rt_->atom_array) VisitRootHeapObj(rt_->atom_array, local_idx);
  if (rt_->atom_hash) VisitRootHeapObj(rt_->atom_hash, local_idx);
  if (rt_->shape_hash) VisitRootHeapObj(rt_->shape_hash, local_idx);
#ifdef ENABLE_TRACING_GC
  if (rt_->boilerplateArg0) VisitRootHeapObj(rt_->boilerplateArg0, local_idx);
  if (rt_->boilerplateArg1) VisitRootHeapObj(rt_->boilerplateArg1, local_idx);
  if (rt_->boilerplateArg2) VisitRootHeapObj(rt_->boilerplateArg2, local_idx);
  if (rt_->boilerplateArg3) VisitRootHeapObj(rt_->boilerplateArg3, local_idx);
#endif
  // global handles
  if (rt_->global_handles_) rt_->global_handles_->IterateAllRoots(local_idx, 0);
  if (rt_->qjsvaluevalue_allocator)
    rt_->qjsvaluevalue_allocator->IterateAllRoots(local_idx);
  atomic_release_local_idx(m, local_idx);
}

void Visitor::ScanHandles() noexcept {
  mstate m = &rt_->malloc_state.allocate_state;
  int local_idx = atomic_acqurie_local_idx(m);
  while (local_idx == -1) {
#ifndef _WIN32
    sched_yield();
#endif
    local_idx = atomic_acqurie_local_idx(m);
  }
  PtrHandles *handles = rt_->ptr_handles;
  int size, i;
  // handles
  size = handles->GetHeapObjIdx();
  HeapStruct *heap_struct_handles = handles->GetHandles();
  for (i = 0; i < size; i++) {
    VisitRoot(heap_struct_handles[i].ptr, heap_struct_handles[i].type,
              local_idx);
  }
  atomic_release_local_idx(m, local_idx);
}

void Visitor::ScanContext(LEPUSContext *ctx) noexcept {
  mstate m = &rt_->malloc_state.allocate_state;
  int local_idx = atomic_acqurie_local_idx(m);
  while (local_idx == -1) {
#ifndef _WIN32
    sched_yield();
#endif
    local_idx = atomic_acqurie_local_idx(m);
  }
  struct list_head *el, *el1;
  // modules
  list_for_each_safe(el, el1, &ctx->loaded_modules) {
    LEPUSModuleDef *m = list_entry(el, LEPUSModuleDef, link);
    VisitRootHeapObj(m, local_idx);
  }

  VisitRootLEPUSValue(ctx->global_obj, local_idx);
  VisitRootLEPUSValue(ctx->global_var_obj, local_idx);

  VisitRootLEPUSValue(ctx->throw_type_error, local_idx);
  VisitRootLEPUSValue(ctx->eval_obj, local_idx);

  PushObjLEPUSValue(ctx->array_proto_values, local_idx);
  for (int i = 0; i < JS_NATIVE_ERROR_COUNT; i++) {
    VisitRootLEPUSValue(ctx->native_error_proto[i], local_idx);
  }
  for (int i = 0; i < rt_->class_count; i++) {
    VisitRootLEPUSValue(ctx->class_proto[i], local_idx);
  }
  VisitRootHeapObj(ctx->class_proto, local_idx);
  VisitRootLEPUSValue(ctx->iterator_proto, local_idx);
  VisitRootLEPUSValue(ctx->async_iterator_proto, local_idx);
  VisitRootLEPUSValue(ctx->promise_ctor, local_idx);
  VisitRootLEPUSValue(ctx->regexp_ctor, local_idx);
  VisitRootLEPUSValue(ctx->function_ctor, local_idx);
  VisitRootLEPUSValue(ctx->function_proto, local_idx);
  if (ctx->array_shape) {
    VisitRootHeapObj(get_alloc_from_shape(ctx->array_shape), local_idx);
  }
  // napi scope
  NAPIHandleScope *cur_scope = ctx->napi_scope;
  while (cur_scope) {
    NAPIHandleScope::Handle *cur_handle = cur_scope->GetHandle();
    while (cur_handle) {
      VisitRootLEPUSValue(cur_handle->value, local_idx);
      cur_handle = cur_handle->prev;
    }
    cur_scope = cur_scope->GetPrevScope();
  }
#ifdef ENABLE_QUICKJS_DEBUGGER
  if (ctx->debugger_info) {
    VisitRootHeapObj(ctx->debugger_info->source_code, local_idx);
    VisitRootHeapObj(ctx->debugger_info, local_idx);
  }
#endif
  if (ctx->lynx_target_sdk_version) {
    VisitRootHeapObj(ctx->lynx_target_sdk_version, local_idx);
  }
  if (ctx->fg_ctx) VisitRootHeapObj(ctx->fg_ctx, local_idx);
  atomic_release_local_idx(m, local_idx);
}

void Visitor::VisitRoot(void *ptr, HandleType type, int local_idx) noexcept {
  switch (type) {
    case HANDLE_TYPE_HEAP_OBJ:
      VisitRootHeapObj(*(reinterpret_cast<void **>(ptr)), local_idx);
      break;
    case HANDLE_TYPE_DIR_HEAP_OBJ:
      VisitRootHeapObj(ptr, local_idx);
      break;
    case HANDLE_TYPE_LEPUS_VALUE:
      VisitRootLEPUSValue(reinterpret_cast<LEPUSValue *>(ptr), local_idx);
      break;
    case HANDLE_TYPE_LEPUS_TOKEN:
      VisitRootJSToken(reinterpret_cast<JSToken *>(ptr), local_idx);
      break;
    case HANDLE_TYPE_BC_READER_STATE:
      VisitRootBCReaderState(reinterpret_cast<BCReaderState *>(ptr), local_idx);
      break;
    case HANDLE_TYPE_VALUE_BUFFER:
      VisitRootValueBuffer(reinterpret_cast<ValueBuffer *>(ptr), local_idx);
      break;
    case HANDLE_TYPE_CSTRING:
      VisitRootCString(*(reinterpret_cast<char **>(ptr)), local_idx);
      break;
    case HANDLE_TYPE_UNDEFINED:
      break;
    default:
      abort();
  }
}

void Visitor::VisitRootHeapObj(void *ptr, int local_idx) noexcept {
  if (!ptr) return;
  DCHECK(check_valid_ptr(rt_, ptr));
  if (!is_marked_multi(ptr)) {
    set_mark_multi(ptr);
    VisitEntry(ptr, local_idx);
  }
  void *current_ptr = nullptr;
  while (!queue[local_idx]->IsEmpty()) {
    current_ptr = queue[local_idx]->DeQueue();
    if (current_ptr != nullptr && !is_marked_multi(current_ptr)) {
      set_mark_multi(current_ptr);
      VisitEntry(current_ptr, local_idx);
    }

    // if (queue[local_idx]->GetCount() >= 20000) {
    //   Queue *q = queue[local_idx];
    //   queue[local_idx] = new Queue(rt_);
    //   rt_->workerThreadPool->AddTask(new ByteLambdaTask(
    //       [this, q, ptr](size_t) { VisitRootHeapObjForTask(q, ptr); }));
    // }
  }
}

void Visitor::VisitRootHeapObjForTask(Queue *q, void *ptr) noexcept {
  mstate m = &rt_->malloc_state.allocate_state;
  int local_idx = atomic_acqurie_local_idx(m);
  while (local_idx == -1) {
#ifndef _WIN32
    sched_yield();
#endif
    local_idx = atomic_acqurie_local_idx(m);
  }
  delete queue[local_idx];
  queue[local_idx] = q;
  void *current_ptr = nullptr;
  while (!queue[local_idx]->IsEmpty()) {
    current_ptr = queue[local_idx]->DeQueue();
    if (current_ptr != nullptr && !is_marked_multi(current_ptr)) {
      set_mark_multi(current_ptr);
      VisitEntry(current_ptr, local_idx);
    }

    if (queue[local_idx]->GetCount() >= 40000) {
      Queue *q = new Queue(rt_);
      queue[local_idx]->Split(queue[local_idx]->GetCount() / 2, q);
      rt_->workerThreadPool->AddTask(new ByteLambdaTask(
          [this, q, ptr](size_t) { VisitRootHeapObjForTask(q, ptr); }));
    }
  }
  atomic_release_local_idx(m, local_idx);
}

void set_mark_func(LEPUSRuntime *rt, LEPUSValueConst val, uint64_t trace_tool) {
  rt->gc->GetVisitor()->VisitRootLEPUSValue(val, (int)trace_tool);
}

void Visitor::VisitRootLEPUSValue(LEPUSValue &val, int local_idx) noexcept {
  int64_t tag = LEPUS_VALUE_GET_TAG(val);
  void *ptr = LEPUS_VALUE_GET_PTR(val);
  switch (tag) {
    case LEPUS_TAG_STRING:
      if (IsConstString(ptr)) break;
      VisitRootHeapObj(ptr, local_idx);
      break;
    case LEPUS_TAG_SEPARABLE_STRING:
    case LEPUS_TAG_FUNCTION_BYTECODE:
    case LEPUS_TAG_LEPUS_REF:
    case LEPUS_TAG_SYMBOL:
    case LEPUS_TAG_BIG_INT:
    case LEPUS_TAG_OBJECT: {
      VisitRootHeapObj(ptr, local_idx);
      break;
    }
    case LEPUS_TAG_Atom: {
      JSAtom atom = LEPUS_VALUE_GET_INT(val);
      if (__JS_AtomIsTaggedInt(atom) || __JS_AtomIsConst(atom)) break;
      VisitRootHeapObj(rt_->atom_array[atom], local_idx);
      break;
    }
    default:
      // printf("__JS_FreeValue: unknown tag=%d\n", tag);
      break;
  }
}

void Visitor::VisitRootBCReaderState(BCReaderState *s, int local_idx) noexcept {
  DCHECK(s != nullptr);
  if (s->idx_to_atom) {
    uint32_t atom_count = s->idx_to_atom_count;
    for (uint32_t i = 0; i < atom_count; i++) {
      VisitJSAtom(s->idx_to_atom[i], local_idx);
    }
    VisitRootHeapObj(s->idx_to_atom, local_idx);
  }
}

void Visitor::VisitRootValueBuffer(ValueBuffer *b, int local_idx) noexcept {
  DCHECK(b != nullptr);
  for (int i = 0; i < b->len; i++) {
    VisitRootLEPUSValue(b->arr[i], local_idx);
  }
  if (b->arr != b->def) {
    VisitRootHeapObj(b->arr, local_idx);
  }
}

void Visitor::VisitJSAtom(JSAtom atom, int local_idx) noexcept {
  if (__JS_AtomIsTaggedInt(atom) || __JS_AtomIsConst(atom)) return;
  VisitRootHeapObj(rt_->atom_array[atom], local_idx);
}

// private push
void Visitor::PushObjLEPUSValue(LEPUSValue &val, int local_idx) noexcept {
  int64_t tag = LEPUS_VALUE_GET_TAG(val);
  switch (tag) {
    case LEPUS_TAG_STRING:
    case LEPUS_TAG_SEPARABLE_STRING:
    case LEPUS_TAG_OBJECT:
    case LEPUS_TAG_FUNCTION_BYTECODE:
    case LEPUS_TAG_SYMBOL:
    // trace_gc, todo
    case LEPUS_TAG_BIG_INT:
#ifdef ENABLE_LEPUSNG
    case LEPUS_TAG_LEPUS_REF:
#endif
      queue[local_idx]->EnQueue(LEPUS_VALUE_GET_PTR(val));
      break;
    case LEPUS_TAG_Atom: {
      JSAtom atom = LEPUS_VALUE_GET_INT(val);
      queue[local_idx]->EnQueue(rt_->atom_array[atom]);
      break;
    }
    default:
      break;
  }
}

void Visitor::PushObjAtom(JSAtom atom, int local_idx) noexcept {
  if (__JS_AtomIsTaggedInt(atom) || __JS_AtomIsConst(atom)) return;
  assert(atom < rt_->atom_size);
  JSAtomStruct *p = rt_->atom_array[atom];
  assert(!atom_is_free(p));
  queue[local_idx]->EnQueue(static_cast<void *>(p));
}

void Visitor::PushObjJSAsyncFunctionState(JSAsyncFunctionState *s,
                                          int local_idx) noexcept {
  // async_func_free
  PushObjJStackFrame(&s->frame, local_idx);
  PushObjLEPUSValue(s->this_val, local_idx);
}

void Visitor::PushObjJStackFrame(LEPUSStackFrame *sf, int local_idx) noexcept {
  // free: async_func_free()
  JSVarRef *var_ref;
  list_head *el;
  if (sf->var_refs) {
    VisitRootHeapObj(sf->var_refs, local_idx);
  }

  list_for_each(el, &sf->var_ref_list) {
    var_ref = list_entry(el, JSVarRef, link);
    VisitRootHeapObj(var_ref, local_idx);
  }

  if (sf->arg_buf) {
    for (LEPUSValue *sp = sf->arg_buf; sp < sf->cur_sp; sp++) {
      PushObjLEPUSValue(sp, local_idx);
    }
    queue[local_idx]->EnQueue(sf->arg_buf);
  }
  PushObjLEPUSValue(sf->cur_func, local_idx);
}

void Visitor::PushBytecodeAtoms(const uint8_t *bc_buf, int bc_len,
                                int use_short_opcodes, int local_idx) noexcept {
  int pos, len, op;
  JSAtom atom;
  const JSOpCode *oi;

  pos = 0;
  while ((pos + 1) < bc_len) {
    op = bc_buf[pos];
    if (use_short_opcodes)
      oi = &short_opcode_info(op);
    else
      oi = &opcode_info[op];

    len = oi->size;
    switch (oi->fmt) {
      case OP_FMT_atom:
      case OP_FMT_atom_u8:
      case OP_FMT_atom_u16:
      case OP_FMT_atom_label_u8:
      case OP_FMT_atom_label_u16:
        atom = get_u32(bc_buf + pos + 1);
        PushObjAtom(atom, local_idx);
        break;
      default:
        break;
    }
    pos += len;
  }
}

void Visitor::PushObjJSRegExp(JSRegExp *re, int local_idx) noexcept {
  // js_regexp_finalizer
  // class_id: JS_CLASS_REGEXP
  // p->u.regexp
  queue[local_idx]->EnQueue(re->pattern);
  queue[local_idx]->EnQueue(re->bytecode);
}

void Visitor::PushObjFunc(LEPUSObject *obj, int local_idx) noexcept {
  // js_bytecode_function_finalizer
  LEPUSObject *p = obj->u.func.home_object;
  if (p) queue[local_idx]->EnQueue(p);
  LEPUSFunctionBytecode *b = obj->u.func.function_bytecode;
  if (b) {
    JSVarRef **var_refs = obj->u.func.var_refs;
    if (var_refs) {
      for (int i = 0; i < b->closure_var_count; i++) {
        queue[local_idx]->EnQueue(var_refs[i]);
      }
      queue[local_idx]->EnQueue(var_refs);
    }
    queue[local_idx]->EnQueue(b);
  }
}

void Visitor::PushObjArray(LEPUSObject *obj, int local_idx) noexcept {
  // js_array_finalizer
  if (!obj->u.array.u.values) return;
  for (int i = 0; i < obj->u.array.count; i++) {
    PushObjLEPUSValue(obj->u.array.u.values[i], local_idx);
  }
  queue[local_idx]->EnQueue(obj->u.array.u.values);
}

void Visitor::PushObjRegExp(LEPUSObject *obj, int local_idx) noexcept {
  // js_regexp_finalizer
  JSRegExp *re = &obj->u.regexp;
  queue[local_idx]->EnQueue(re->bytecode);
  queue[local_idx]->EnQueue(re->pattern);
}

void Visitor::PushObjProperty(JSProperty *pr, int prop_flags,
                              int local_idx) noexcept {
  if (unlikely(prop_flags & LEPUS_PROP_TMASK)) {
    if ((prop_flags & LEPUS_PROP_TMASK) == LEPUS_PROP_GETSET) {
      if (pr->u.getset.getter) queue[local_idx]->EnQueue(pr->u.getset.getter);
      if (pr->u.getset.setter) queue[local_idx]->EnQueue(pr->u.getset.setter);
    } else if ((prop_flags & LEPUS_PROP_TMASK) == LEPUS_PROP_VARREF) {
      queue[local_idx]->EnQueue(pr->u.var_ref);
    } else if ((prop_flags & LEPUS_PROP_TMASK) == LEPUS_PROP_AUTOINIT) {
      /* nothing to do */
    }
  } else {
    PushObjLEPUSValue(pr->u.value, local_idx);
  }
}

void Visitor::VisitJShape(void *ptr, int local_idx) noexcept {
  int hash_size = get_hash_size(ptr);
  JSShape *sh = get_shape_from_alloc(ptr, hash_size);
  if (sh->proto != NULL) {
    queue[local_idx]->EnQueue(sh->proto);
  }
  JSShapeProperty *pr = get_shape_prop(sh);
  for (int i = 0; i < sh->prop_count; i++) {
    PushObjAtom(pr->atom, local_idx);
    pr++;
  }
}

void Visitor::VisitJSFunctionBytecode(void *ptr, int local_idx) noexcept {
  /*
  free_function_bytecode
  */
  LEPUSFunctionBytecode *b = static_cast<LEPUSFunctionBytecode *>(ptr);
  PushBytecodeAtoms(b->byte_code_buf, b->byte_code_len, TRUE, local_idx);
  int i;
  if (b->vardefs) {
    for (i = 0; i < b->arg_count + b->var_count; i++) {
      PushObjAtom(b->vardefs[i].var_name, local_idx);
    }
  }
  if (b->cpool) {
    for (i = 0; i < b->cpool_count; i++) {
      PushObjLEPUSValue(b->cpool[i], local_idx);
    }
  }
  for (i = 0; i < b->closure_var_count; i++) {
    LEPUSClosureVar *cv = &b->closure_var[i];
    PushObjAtom(cv->var_name, local_idx);
  }
  PushObjAtom(b->func_name, local_idx);
  // debug
  if (b->has_debug) {
    PushObjAtom(b->debug.filename, local_idx);
#ifdef ENABLE_QUICKJS_DEBUGGER
    if (b->debug.func_name) {
      queue[local_idx]->EnQueue(b->debug.func_name);
    }
#endif
    queue[local_idx]->EnQueue(b->debug.pc2line_buf);
    if (b->debug.caller_size) {
      for (uint32_t i = 0, count = b->debug.caller_size; i < count; ++i) {
        auto &slot = b->debug.caller_slots[i];
        if (slot.is_str) {
          queue[local_idx]->EnQueue(
              reinterpret_cast<void *>(const_cast<char *>(slot.str)));
        }
      }
      queue[local_idx]->EnQueue(b->debug.caller_slots);
    }
  }
  return;
}

void Visitor::VisitJSObject(void *ptr, int local_idx) noexcept {
  // free_object
  LEPUSObject *obj = static_cast<LEPUSObject *>(ptr);
  JSShape *sh;
  JSShapeProperty *pr;
  // shape and prop
  sh = obj->shape;
  if (sh) {
    queue[local_idx]->EnQueue(get_alloc_from_shape(sh));
    if (obj->prop) {
      pr = get_shape_prop(sh);
      for (int i = 0; i < sh->prop_count; i++) {
        PushObjProperty(&obj->prop[i], pr->flags, local_idx);
        pr++;
      }
      queue[local_idx]->EnQueue(obj->prop);
    }
  }
  // first_weak_ref
  if (unlikely(obj->first_weak_ref)) {
    /* second pass to free the values to avoid modifying the weak
     reference list while traversing it. */
    WeakRefRecord *wr;
    for (wr = obj->first_weak_ref; wr != nullptr; wr = wr->next_weak_ref) {
      switch (wr->kind) {
        case WEAK_REF_KIND_WEAK_MAP: {
          queue[local_idx]->EnQueue(wr->u.map_record);
          PushObjLEPUSValue(wr->u.map_record->value, local_idx);
        } break;
        case WEAK_REF_KIND_FINALIZATION_REGISTRY: {
          FinalizationRegistryEntry *fin_node = wr->u.fin_node;
          queue[local_idx]->EnQueue(fin_node);
          PushObjLEPUSValue(fin_node->held_value, local_idx);
          PushObjLEPUSValue(fin_node->token, local_idx);
        } break;
        default:
          break;
      }
      queue[local_idx]->EnQueue(wr);
    }
  }
  // finalizer
  switch (obj->class_id) {
    case JS_CLASS_ARRAY:
    case JS_CLASS_ARGUMENTS:
      PushObjArray(obj, local_idx);
      break;
    case JS_CLASS_BOUND_FUNCTION:
      queue[local_idx]->EnQueue(obj->u.bound_function);
      break;
    case JS_CLASS_C_FUNCTION_DATA:
      queue[local_idx]->EnQueue(obj->u.c_function_data_record);
      break;
    case JS_CLASS_FOR_IN_ITERATOR:
      queue[local_idx]->EnQueue(obj->u.for_in_iterator);
      break;
    case JS_CLASS_ARRAY_BUFFER:
    case JS_CLASS_SHARED_ARRAY_BUFFER:
      queue[local_idx]->EnQueue(obj->u.array_buffer);
      break;
    case JS_CLASS_UINT8C_ARRAY:
    case JS_CLASS_INT8_ARRAY:
    case JS_CLASS_UINT8_ARRAY:
    case JS_CLASS_INT16_ARRAY:
    case JS_CLASS_UINT16_ARRAY:
    case JS_CLASS_INT32_ARRAY:
    case JS_CLASS_UINT32_ARRAY:
    case JS_CLASS_BIG_INT64_ARRAY:
    case JS_CLASS_BIG_UINT64_ARRAY:
    case JS_CLASS_FLOAT32_ARRAY:
    case JS_CLASS_FLOAT64_ARRAY:
    case JS_CLASS_DATAVIEW:
      queue[local_idx]->EnQueue(obj->u.typed_array);
      break;
    case JS_CLASS_MAP:
    case JS_CLASS_SET:
    case JS_CLASS_WEAKMAP:
    case JS_CLASS_WEAKSET:
      // js_map_finalizer
      queue[local_idx]->EnQueue(obj->u.map_state);
      break;
    case JS_CLASS_MAP_ITERATOR:
    case JS_CLASS_SET_ITERATOR:
      queue[local_idx]->EnQueue(obj->u.map_iterator_data);
      break;
    case JS_CLASS_ARRAY_ITERATOR:
    case JS_CLASS_STRING_ITERATOR:
      queue[local_idx]->EnQueue(obj->u.array_iterator_data);
      break;
    case JS_CLASS_REGEXP_STRING_ITERATOR:
      queue[local_idx]->EnQueue(obj->u.regexp_string_iterator_data);
      break;
    case JS_CLASS_GENERATOR:
      queue[local_idx]->EnQueue(obj->u.generator_data);
      break;
    case JS_CLASS_PROXY:
      queue[local_idx]->EnQueue(obj->u.proxy_data);
      break;
    case JS_CLASS_PROMISE:
      queue[local_idx]->EnQueue(obj->u.promise_data);
      break;
    case JS_CLASS_PROMISE_RESOLVE_FUNCTION:
    case JS_CLASS_PROMISE_REJECT_FUNCTION:
      queue[local_idx]->EnQueue(obj->u.promise_function_data);
      break;
    case JS_CLASS_ASYNC_FUNCTION_RESOLVE:
    case JS_CLASS_ASYNC_FUNCTION_REJECT:
      queue[local_idx]->EnQueue(obj->u.async_function_data);
      break;
    case JS_CLASS_ASYNC_FROM_SYNC_ITERATOR:
      queue[local_idx]->EnQueue(obj->u.async_from_sync_iterator_data);
      break;
    case JS_CLASS_ASYNC_GENERATOR:
      queue[local_idx]->EnQueue(obj->u.async_generator_data);
      break;
    case JS_CLASS_BYTECODE_FUNCTION:
    case JS_CLASS_GENERATOR_FUNCTION:
    case JS_CLASS_ASYNC_FUNCTION:
    case JS_CLASS_ASYNC_GENERATOR_FUNCTION:
      PushObjFunc(obj, local_idx);
      break;
    case JS_CLASS_C_FUNCTION:
      break;
    case JS_CLASS_REGEXP:
      PushObjRegExp(obj, local_idx);
      break;
    case JS_CLASS_NUMBER:
    case JS_CLASS_STRING:
    case JS_CLASS_BOOLEAN:
    case JS_CLASS_SYMBOL:
    case JS_CLASS_DATE:
      PushObjLEPUSValue(obj->u.object_data, local_idx);
      break;
    case JS_CLASS_WeakRef:
      // js_weakref_finalizer
      queue[local_idx]->EnQueue(obj->u.weak_ref_data);
      break;
    case JS_CLASS_FinalizationRegistry:
      // js_finalizationRegistry_finalizer
      queue[local_idx]->EnQueue(obj->u.opaque);
      break;
    case JS_CLASS_BIG_INT:
      PushObjLEPUSValue(obj->u.object_data, local_idx);
      break;
    default:
      break;
  }
  if (JS_OBJECT_IS_OUTER(obj)) {
    LEPUSClassGCMark *gc_mark = rt_->class_array[obj->class_id].gc_mark;
    if (gc_mark) {
      (*gc_mark)(rt_, LEPUS_MKPTR(LEPUS_TAG_OBJECT, obj), set_mark_func,
                 local_idx);
    }
  }
}

void Visitor::VisitJSBoundFunction(void *ptr, int local_idx) noexcept {
  // free: js_bound_function_finalizer
  // class_id: JS_CLASS_BOUND_FUNCTION
  // p->u.bound_function
  JSBoundFunction *bf = static_cast<JSBoundFunction *>(ptr);
  PushObjLEPUSValue(bf->func_obj, local_idx);
  PushObjLEPUSValue(bf->this_val, local_idx);
  for (int i = 0; i < bf->argc; i++) {
    PushObjLEPUSValue(bf->argv[i], local_idx);
  }
}
void Visitor::VisitJSCFunctionDataRecord(void *ptr, int local_idx) noexcept {
  // JS_CLASS_C_FUNCTION_DATA
  // js_c_function_data_finalizer
  // u.c_function_data_record
  JSCFunctionDataRecord *s = static_cast<JSCFunctionDataRecord *>(ptr);
  for (int i = 0; i < s->data_len; i++) {
    PushObjLEPUSValue(s->data[i], local_idx);
  }
}

void Visitor::VisitJSForInIterator(void *ptr, int local_idx) noexcept {
  // free: js_for_in_iterator_finalizer
  // class_id: JS_CLASS_FOR_IN_ITERATOR
  // p->u.for_in_iterator
  JSForInIterator *it = static_cast<JSForInIterator *>(ptr);
  PushObjLEPUSValue(it->obj, local_idx);
}

void Visitor::VisitJSArrayBuffer(void *ptr, int local_idx) noexcept {
  // JS_CLASS_ARRAY_BUFFER
  // js_array_buffer_finalizer
  // p->u.array_buffer
  JSArrayBuffer *abuf = static_cast<JSArrayBuffer *>(ptr);
  if (abuf->from_js_heap) queue[local_idx]->EnQueue(abuf->data);
}

void Visitor::VisitJSTypedArray(void *ptr, int local_idx) noexcept {
  // JS_CLASS_DATAVIEW
  // js_typed_array_finalizer
  // u.array
  JSTypedArray *ta = static_cast<JSTypedArray *>(ptr);
  queue[local_idx]->EnQueue(ta->buffer);
}

void Visitor::VisitJSMapState(void *ptr, int local_idx) noexcept {
  // JS_CLASS_MAP..JS_CLASS_WEAKSET
  // js_map_finalizer
  // u.map_state
  JSMapState *s = static_cast<JSMapState *>(ptr);
  struct list_head *el, *el1;
  JSMapRecord *mr;
  list_for_each_safe(el, el1, &s->records) {
    mr = list_entry(el, JSMapRecord, link);
    if (!mr->empty) {
      if (!s->is_weak) PushObjLEPUSValue(mr->key, local_idx);
      PushObjLEPUSValue(mr->value, local_idx);
    }
    queue[local_idx]->EnQueue(mr);  // tag == 1
  }
  queue[local_idx]->EnQueue(s->hash_table);
}

void Visitor::VisitJSMapIteratorData(void *ptr, int local_idx) noexcept {
  // JS_CLASS_MAP_ITERATOR JS_CLASS_SET_ITERATOR
  // js_map_iterator_finalizer
  // u.map_iterator_data
  JSMapIteratorData *it = static_cast<JSMapIteratorData *>(ptr);
  if (it->cur_record) {
    queue[local_idx]->EnQueue(it->cur_record);
  }
  PushObjLEPUSValue(it->obj, local_idx);
}

void Visitor::VisitJSArrayIteratorData(void *ptr, int local_idx) noexcept {
  // JS_CLASS_ARRAY_ITERATOR JS_CLASS_STRING_ITERATOR
  // js_array_iterator_finalizer
  // u.array_iterator_data
  JSArrayIteratorData *it = static_cast<JSArrayIteratorData *>(ptr);
  PushObjLEPUSValue(it->obj, local_idx);
}

void Visitor::VisitJSRegExpStringIteratorData(void *ptr,
                                              int local_idx) noexcept {
  // JS_CLASS_REGEXP_STRING_ITERATOR
  // js_regexp_string_iterator_finalizer
  // u.regexp_string_iterator_data
  JSRegExpStringIteratorData *it =
      static_cast<JSRegExpStringIteratorData *>(ptr);
  PushObjLEPUSValue(it->iterating_regexp, local_idx);
  PushObjLEPUSValue(it->iterated_string, local_idx);
}

void Visitor::VisitJSGeneratorData(void *ptr, int local_idx) noexcept {
  // JS_CLASS_GENERATOR
  // js_generator_finalizer
  // u.generator_data
  JSGeneratorData *s = static_cast<JSGeneratorData *>(ptr);
  if (s->state == JS_GENERATOR_STATE_COMPLETED) return;
  PushObjJSAsyncFunctionState(&s->func_state, local_idx);
}

void Visitor::VisitJSProxyData(void *ptr, int local_idx) noexcept {
  // JS_CLASS_PROXY
  // js_proxy_finalizer
  // p->u.proxy_data
  JSProxyData *s = static_cast<JSProxyData *>(ptr);
  PushObjLEPUSValue(s->target, local_idx);
  PushObjLEPUSValue(s->handler, local_idx);
  PushObjLEPUSValue(s->proto, local_idx);
}

void Visitor::VisitJSPromiseData(void *ptr, int local_idx) noexcept {
  // JS_CLASS_PROMISE
  // js_promise_finalizer
  // u.promise_data
  JSPromiseData *s = static_cast<JSPromiseData *>(ptr);
  struct list_head *el, *el1;
  for (int i = 0; i < 2; i++) {
    list_for_each_safe(el, el1, &s->promise_reactions[i]) {
      JSPromiseReactionData *rd = list_entry(el, JSPromiseReactionData, link);
      queue[local_idx]->EnQueue(rd);
      // promise_reaction_data_free(rt, rd);
    }
  }
  PushObjLEPUSValue(s->promise_result, local_idx);
}

void Visitor::VisitJSPromiseReactionData(void *ptr, int local_idx) noexcept {
  // promise_reaction_data_free
  JSPromiseReactionData *rd = static_cast<JSPromiseReactionData *>(ptr);
  PushObjLEPUSValue(rd->resolving_funcs[0], local_idx);
  PushObjLEPUSValue(rd->resolving_funcs[1], local_idx);
  PushObjLEPUSValue(rd->handler, local_idx);
}

void Visitor::VisitJSPromiseFunctionData(void *ptr, int local_idx) noexcept {
  // JS_CLASS_PROMISE_RESOLVE_FUNCTION
  // js_promise_resolve_function_finalizer
  // u.promise_function_data
  JSPromiseFunctionData *s = static_cast<JSPromiseFunctionData *>(ptr);
  queue[local_idx]->EnQueue(s->presolved);
  PushObjLEPUSValue(s->promise, local_idx);
}

void Visitor::VisitJSAsyncFunctionData(void *ptr, int local_idx) noexcept {
  // JS_CLASS_ASYNC_FUNCTION_REJECT
  // js_async_function_resolve_finalizer, asyn_function_free
  // u.async_function_data
  JSAsyncFunctionData *obj = static_cast<JSAsyncFunctionData *>(ptr);
  if (obj->is_active) {
    PushObjJSAsyncFunctionState(&obj->func_state, local_idx);
  }
  PushObjLEPUSValue(obj->resolving_funcs[0], local_idx);
  PushObjLEPUSValue(obj->resolving_funcs[1], local_idx);
}

void Visitor::VisitJSAsyncFromSyncIteratorData(void *ptr,
                                               int local_idx) noexcept {
  // JS_CLASS_ASYNC_FROM_SYNC_ITERATOR
  // js_async_from_sync_iterator_finalizer
  // async_from_sync_iterator_data
  JSAsyncFromSyncIteratorData *s =
      static_cast<JSAsyncFromSyncIteratorData *>(ptr);
  PushObjLEPUSValue(s->sync_iter, local_idx);
  PushObjLEPUSValue(s->next_method, local_idx);
}

void Visitor::VisitJSAsyncGeneratorData(void *ptr, int local_idx) noexcept {
  // JS_CLASS_ASYNC_GENERATOR
  // js_async_generator_finalizer, js_async_generator_free
  // u.async_generator_data
  JSAsyncGeneratorData *s = static_cast<JSAsyncGeneratorData *>(ptr);
  struct list_head *el, *el1;
  JSAsyncGeneratorRequest *req;

  list_for_each_safe(el, el1, &s->queue) {
    req = list_entry(el, JSAsyncGeneratorRequest, link);
    PushObjLEPUSValue(req->result, local_idx);
    PushObjLEPUSValue(req->promise, local_idx);
    PushObjLEPUSValue(req->resolving_funcs[0], local_idx);
    PushObjLEPUSValue(req->resolving_funcs[1], local_idx);
    queue[local_idx]->EnQueue(req);
  }
  if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED &&
      s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) {
    PushObjJSAsyncFunctionState(&s->func_state, local_idx);
  }
}

#ifdef ENABLE_QUICKJS_DEBUGGER
void Visitor::VisitJSScriptSource(void *ptr, int local_idx) noexcept {
  // DebuggerFreeScript
  LEPUSScriptSource *script = static_cast<LEPUSScriptSource *>(ptr);
  queue[local_idx]->EnQueue(script->url);
  queue[local_idx]->EnQueue(script->source);
  queue[local_idx]->EnQueue(script->hash);
  if (script->source_map_url) {
    queue[local_idx]->EnQueue(script->source_map_url);
  }
}
#endif

void Visitor::VisitJSPropertyEnum(void *ptr, int local_idx) noexcept {
  // js_free_prop_enum();
  int len = get_heap_obj_len(ptr);
  LEPUSPropertyEnum *tab = static_cast<LEPUSPropertyEnum *>(ptr);
  for (int i = 0; i < len; i++) {
    PushObjAtom(tab[i].atom, local_idx);
  }
}

void Visitor::VisitJSModuleDef(void *ptr, int local_idx) noexcept {
  // LEPUS_TAG_MODULE
  LEPUSModuleDef *m = static_cast<LEPUSModuleDef *>(ptr);
  PushObjAtom(m->module_name, local_idx);

  int i;
  for (i = 0; i < m->req_module_entries_count; i++) {
    JSReqModuleEntry *rme = &m->req_module_entries[i];
    PushObjAtom(rme->module_name, local_idx);
  }
  queue[local_idx]->EnQueue(m->req_module_entries);  // visit_nothing

  for (i = 0; i < m->export_entries_count; i++) {
    JSExportEntry *me = &m->export_entries[i];
    if (me->export_type == JS_EXPORT_TYPE_LOCAL) {
      JSVarRef *var_ref = me->u.local.var_ref;
      if (var_ref) {
        queue[local_idx]->EnQueue(var_ref);  // alloc_tag_LEPUSVarRef
      }
    }
    PushObjAtom(me->export_name, local_idx);
    PushObjAtom(me->local_name, local_idx);
  }
  queue[local_idx]->EnQueue(m->export_entries);

  queue[local_idx]->EnQueue(m->star_export_entries);

  for (i = 0; i < m->import_entries_count; i++) {
    JSImportEntry *mi = &m->import_entries[i];
    PushObjAtom(mi->import_name, local_idx);
  }
  queue[local_idx]->EnQueue(m->import_entries);
  PushObjLEPUSValue(m->module_ns, local_idx);
  PushObjLEPUSValue(m->func_obj, local_idx);
  PushObjLEPUSValue(m->eval_exception, local_idx);
}

void Visitor::VisitJSFunctionDef(void *ptr, int local_idx) noexcept {
  // js_free_function_def
  JSFunctionDef *fd = static_cast<JSFunctionDef *>(ptr);
  int i;
  struct list_head *el, *el1;
  list_for_each_safe(el, el1, &fd->child_list) {
    JSFunctionDef *fd1 = list_entry(el, JSFunctionDef, link);
    queue[local_idx]->EnQueue(fd1);
  }

  DynBuf *dbuf;
  if (fd->byte_code.buf) {
    PushBytecodeAtoms(fd->byte_code.buf, fd->byte_code.size,
                      fd->use_short_opcodes, local_idx);
    dbuf = &fd->byte_code;
    queue[local_idx]->EnQueue(dbuf->buf);
  }

  queue[local_idx]->EnQueue(fd->jump_slots);
  if (fd->label_slots) {
    queue[local_idx]->EnQueue(fd->label_slots);
    for (int i = 0; i < fd->label_count; i++) {
      LabelSlot *ls = &fd->label_slots[i];
      for (RelocEntry *re = ls->first_reloc; re != NULL; re = re->next) {
        queue[local_idx]->EnQueue(re);
      }
    }
  }

  queue[local_idx]->EnQueue(fd->line_number_slots);

  if (fd->cpool) {
    for (i = 0; i < fd->cpool_count; i++) {
      PushObjLEPUSValue(fd->cpool[i], local_idx);
    }
    queue[local_idx]->EnQueue(fd->cpool);
  }

  PushObjAtom(fd->func_name, local_idx);

  for (i = 0; i < fd->var_count; i++) {
    PushObjAtom(fd->vars[i].var_name, local_idx);
  }
  queue[local_idx]->EnQueue(fd->vars);
  for (i = 0; i < fd->arg_count; i++) {
    PushObjAtom(fd->args[i].var_name, local_idx);
  }
  queue[local_idx]->EnQueue(fd->args);

  for (i = 0; i < fd->hoisted_def_count; i++) {
    PushObjAtom(fd->hoisted_def[i].var_name, local_idx);
  }
  queue[local_idx]->EnQueue(fd->hoisted_def);

  if (fd->closure_var) {
    for (i = 0; i < fd->closure_var_count; i++) {
      LEPUSClosureVar *cv = &fd->closure_var[i];
      PushObjAtom(cv->var_name, local_idx);
    }
    queue[local_idx]->EnQueue(fd->closure_var);
  }

  if (fd->scopes != fd->def_scope_array) {
    queue[local_idx]->EnQueue(fd->scopes);
  }

  PushObjAtom(fd->filename, local_idx);
  dbuf = &fd->pc2line;
  queue[local_idx]->EnQueue(dbuf->buf);

  if (fd->caller_slots) {
    for (uint32_t i = 0, count = fd->caller_count; i < count; ++i) {
      auto &slot = fd->caller_slots[i];
      if (slot.is_str) {
        queue[local_idx]->EnQueue(
            reinterpret_cast<void *>(const_cast<char *>(slot.str)));
      }
    }
    queue[local_idx]->EnQueue(fd->caller_slots);
  }
}

void Visitor::VisitJSValueArray(void *ptr, int local_idx) noexcept {
  LEPUSValue *arr = static_cast<LEPUSValue *>(ptr);
  int len = get_heap_obj_len(ptr);
  for (int i = 0; i < len; i++) {
    PushObjLEPUSValue(arr[i], local_idx);
  }
}

void Visitor::VisitValueSlot(void *ptr, int local_idx) noexcept {
  ValueSlot *array = static_cast<ValueSlot *>(ptr);
  int len = get_heap_obj_len(ptr);
  for (int i = 0; i < len; i++) {
    PushObjLEPUSValue(array[i].val, local_idx);
    queue[local_idx]->EnQueue(array[i].str);
  }
}

void Visitor::VisitJsonStrArray(void *ptr, int local_idx) noexcept {
  int len = get_heap_obj_len(ptr);
  char **str_arr = static_cast<char **>(ptr);
  for (int i = 0; i < len; ++i) {
    VisitRootCString(str_arr[i], local_idx);
  }
}

void Visitor::VisitFinalizationRegistryData(void *ptr, int local_idx) noexcept {
  // js_finalizationRegistry_mark
  auto *frd = reinterpret_cast<FinalizationRegistryData *>(ptr);
  if (!frd) return;
  PushObjLEPUSValue(frd->cbs, local_idx);
  list_head *el;
  list_for_each(el, &frd->entries) {
    FinalizationRegistryEntry *fin_node =
        list_entry(el, FinalizationRegistryEntry, link);
    queue[local_idx]->EnQueue(fin_node);
    PushObjLEPUSValue(fin_node->held_value, local_idx);
    PushObjLEPUSValue(fin_node->token, local_idx);
  }
}

void Finalizer::close_var_refs(LEPUSStackFrame *sf) noexcept {
  struct list_head *el;
  JSVarRef *var_ref;
  list_for_each(el, &sf->var_ref_list) {
    var_ref = list_entry(el, JSVarRef, link);
    var_ref->is_detached = 1;
    var_ref->value = *var_ref->pvalue;
    var_ref->pvalue = &var_ref->value;
  }
  return;
}

void Finalizer::free_atom(LEPUSRuntime *rt, JSAtomStruct *p) noexcept {
  /* free the string structure */
  // Primjs begin
#ifdef ENABLE_LEPUSNG
  JS_FreeStringCache(rt, p);
#endif
  // Primjs end
  if (rt->atom_size == 0) return;
  uint32_t i = p->hash_next; /* atom_index */
  if (p->atom_type != JS_ATOM_TYPE_SYMBOL) {
    JSAtomStruct *p0, *p1;
    uint32_t h0;

    h0 = p->hash & (rt->atom_hash_size - 1);
    i = rt->atom_hash[h0];
    p1 = rt->atom_array[i];
    if (p1 == p) {
      rt->atom_hash[h0] = p1->hash_next;
    } else {
      for (;;) {
        assert(i != 0);
        p0 = p1;
        i = p1->hash_next;
        p1 = rt->atom_array[i];
        if (p1 == p) {
          p0->hash_next = p1->hash_next;
          break;
        }
      }
    }
  }
  /* insert in free atom list */
  rt->atom_array[i] = atom_set_free(rt->atom_free_index);
  rt->atom_free_index = i;
  rt->atom_count--;
  assert(rt->atom_count >= 0);
}

#ifdef ENABLE_LEPUSNG
void Finalizer::JSLepusRefFinalizer(void *ptr) noexcept {
  if (rt_->js_callbacks_.free_value) {
    LEPUSLepusRef *pref = static_cast<LEPUSLepusRef *>(ptr);
    rt_->js_callbacks_.free_value(rt_, LEPUS_MKPTR(LEPUS_TAG_LEPUS_REF, pref));
  }
}
#endif

void Finalizer::JSStringFinalizer(void *ptr) noexcept {
  JSString *str = static_cast<JSString *>(ptr);
  if (str->atom_type) {
    free_atom(rt_, str);
  } else {
#ifdef ENABLE_LEPUSNG
    // <Primjs begin>
    JS_FreeStringCache(rt_, str);
    // <Primjs end>
#endif
  }
}
#ifdef ENABLE_LEPUSNG
void Finalizer::JSStringOnlyFinalizer(void *ptr) noexcept {
  JSString *str = static_cast<JSString *>(ptr);
  JS_FreeStringCache(rt_, str);
}
#endif

void Finalizer::JSShapeFinalizer(void *ptr) noexcept {
  int hash_size = get_hash_size(ptr);  // get hash size
  JSShape *sh = get_shape_from_alloc(ptr, hash_size);
  if (sh->is_hashed) {
    js_shape_hash_unlink(rt_, sh);
  }
}

QJS_STATIC void reset_weak_ref_gc(LEPUSRuntime *rt, LEPUSObject *p) {
  WeakRefRecord *wr;

  /* first pass to remove the records from the WeakMap/WeakSet
     lists */
  for (wr = p->first_weak_ref; wr != NULL; wr = wr->next_weak_ref) {
    switch (wr->kind) {
      case WEAK_REF_KIND_WEAK_MAP: {
        JSMapRecord *mr;
        mr = wr->u.map_record;
        assert(mr->map->is_weak);
        assert(!mr->empty);
        list_del(&mr->link);
        list_del(&mr->hash_link);
      } break;
      case WEAK_REF_KIND_WEAK_REF: {
        WeakRefData *weak_ref = wr->u.weak_ref;
        weak_ref->target = LEPUS_UNDEFINED;
      } break;
      case WEAK_REF_KIND_FINALIZATION_REGISTRY: {
        FinalizationRegistryEntry *fin_node = wr->u.fin_node;
        list_del(&fin_node->link);
      } break;
    }
  }

  /* second pass to free the values to avoid modifying the weak
     reference list while traversing it. */
  for (wr = p->first_weak_ref; wr != nullptr; wr = wr->next_weak_ref) {
    if (wr->kind == WEAK_REF_KIND_FINALIZATION_REGISTRY) {
      FinalizationRegistryEntry *fin_node = wr->u.fin_node;
      FinalizationRegistryData *frd = fin_node->data;
      LEPUSContext *ctx = frd->fg_ctx->ctx;
      if (ctx) {
        LEPUS_Call(ctx, frd->cbs, LEPUS_UNDEFINED, 1, &fin_node->held_value);
      }
    }
  }
  return;
}

void Finalizer::JSObjectFinalizer(void *ptr) noexcept {
  LEPUSObject *obj = static_cast<LEPUSObject *>(ptr);
  js_free_shape(rt_, obj->shape);
  if (unlikely(obj->first_weak_ref)) {
    reset_weak_ref_gc(rt_, obj);
    obj->first_weak_ref = NULL;
  }

  if (JS_OBJECT_IS_OUTER(obj)) {
    LEPUSClassFinalizer *finalizer = rt_->class_array[obj->class_id].finalizer;
    if (finalizer) {
      (*finalizer)(rt_, LEPUS_MKPTR(LEPUS_TAG_OBJECT, obj));
    }
  }
}

void Finalizer::JSObjectOnlyFinalizer(void *ptr) noexcept {
  LEPUSObject *obj = static_cast<LEPUSObject *>(ptr);
  LEPUSClassFinalizer *finalizer = rt_->class_array[obj->class_id].finalizer;
  if (finalizer) {
    (*finalizer)(rt_, LEPUS_MKPTR(LEPUS_TAG_OBJECT, obj));
  }
}

void Finalizer::JSArrayBufferFinalizer(void *ptr) noexcept {
  // not free there
  JSArrayBuffer *abuf = static_cast<JSArrayBuffer *>(ptr);
  if (!abuf->from_js_heap && abuf->free_func)
    abuf->free_func(rt_, abuf->opaque, abuf->data);
}

void Finalizer::JSTypedArrayFinalizer(void *ptr) noexcept {
  JSTypedArray *ta = static_cast<JSTypedArray *>(ptr);
  if (ta) {
    if (JS_IsLiveObject(rt_, LEPUS_MKPTR(LEPUS_TAG_OBJECT, ta->buffer))) {
      list_del(&ta->link);
    }
  }
}
void Finalizer::JSMapStateFinalizer(void *ptr) noexcept {
  JSMapState *s = static_cast<JSMapState *>(ptr);
  struct list_head *el, *el1;
  JSMapRecord *mr;
  list_for_each_safe(el, el1, &s->records) {
    mr = list_entry(el, JSMapRecord, link);
    if (!mr->empty) {
      if (s->is_weak) delete_weak_ref(rt_, LEPUS_VALUE_GET_OBJ(mr->key), mr);
    }
  }
}
void Finalizer::JSMapIteratorDataFinalizer(void *ptr) noexcept {
  JSMapIteratorData *it = static_cast<JSMapIteratorData *>(ptr);
  if (JS_IsLiveObject(rt_, it->obj) && it->cur_record) {
    map_decref_record(rt_, it->cur_record);
  }
}
void Finalizer::JSGeneratorDataFinalizer(void *ptr) noexcept {
  // JS_CLASS_GENERATOR
  // js_generator_finalizer
  // u.generator_data
  JSGeneratorData *s = static_cast<JSGeneratorData *>(ptr);
  if (s->state == JS_GENERATOR_STATE_COMPLETED) return;
  close_var_refs(&s->func_state.frame);
  s->state = JS_GENERATOR_STATE_COMPLETED;
}
void Finalizer::JSAsyncFunctionDataFinalizer(void *ptr) noexcept {
  JSAsyncFunctionData *s = static_cast<JSAsyncFunctionData *>(ptr);
  if (s->is_active) {
    close_var_refs(&s->func_state.frame);
  }
}
void Finalizer::JSAsyncGeneratorDataFinalizer(void *ptr) noexcept {
  JSAsyncGeneratorData *s = static_cast<JSAsyncGeneratorData *>(ptr);
  if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED &&
      s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) {
    close_var_refs(&s->func_state.frame);
  }
}

void Finalizer::JSModuleDefFinalizer(void *ptr) noexcept {
  LEPUSModuleDef *m = static_cast<LEPUSModuleDef *>(ptr);
  list_del(&m->link);
}
void Finalizer::JSFunctionDefFinalizer(void *ptr) noexcept {
  JSFunctionDef *fd = static_cast<JSFunctionDef *>(ptr);
  if (fd->parent && fd->link.prev) {
    /* remove in parent list */
    list_del(&fd->link);
  }
  if (fd->source) {
    system_free(fd->source);
  }
}

void Finalizer::FinalizationRegistryDataFinalizer(void *ptr) noexcept {
  auto *frd = reinterpret_cast<FinalizationRegistryData *>(ptr);
  if (!frd) return;
  list_head *el;
  list_for_each(el, &frd->entries) {
    FinalizationRegistryEntry *fin_node =
        list_entry(el, FinalizationRegistryEntry, link);
    delete_weak_ref(rt_, LEPUS_VALUE_GET_OBJ(fin_node->target), fin_node);
  }
}

void Finalizer::WeakRefDataFinalizer(void *ptr) noexcept {
  auto *wrd = reinterpret_cast<WeakRefData *>(ptr);
  LEPUSValue target = wrd->target;
  if (LEPUS_VALUE_IS_OBJECT(target)) {
    delete_weak_ref(rt_, LEPUS_VALUE_GET_OBJ(target), wrd);
  }
  return;
}

// finalizer
void do_finalizer(void *rt, void *ptr, bool is_only) {
  LEPUSRuntime *rt_ = static_cast<LEPUSRuntime *>(rt);
  if (is_only) {
    rt_->gc->GetFinalizer()->DoFinalizer2(ptr);
  } else {
    rt_->gc->GetFinalizer()->DoFinalizer(ptr);
  }
}

void do_global_finalizer(void *rt) {
  LEPUSRuntime *rt_ = static_cast<LEPUSRuntime *>(rt);
  rt_->global_handles_->GlobalRootsFinalizer();
}

char *LEPUS_GetGCTimingInfo(LEPUSContext *ctx, bool is_start) {
  if (ctx->gc_enable) {
    LEPUSRuntime *rt = ctx->rt;
    char *gc_info = is_start ? rt->gc_info_start_ : rt->gc_info_end_;
    memset(gc_info, 0, BUF_LEN);
    snprintf(gc_info, BUF_LEN,
             "{\n  \"gc_count\" : %zu,\n  \"gc_duration\" : %" PRIu64
             ",\n  \"gc_heapsize\" : %zu,\n  \"rt_info\" : \"%s\"\n}\n",
             rt->gc_cnt, rt->gc->GetGCDuration() / MS,
             rt->malloc_state.allocate_state.footprint / KB, ctx->rt->rt_info);
    return gc_info;
  } else {
    return nullptr;
  }
}
void AddLepusRefCount(LEPUSContext *ctx) {
  LEPUSRuntime *rt = ctx->rt;
  rt->gc->js_ref_count++;
  if (rt->gc->js_ref_count % NUM_OF_LEPUSREF == 0) {
    rt->gc->js_ref_count = 0;
    LEPUS_RunGC(rt);  // Collect JSValue
  }
}
// <Primjs end>

#else
HandleScope::HandleScope(LEPUSRuntime *rt)
    : ptr_handles(nullptr), handle_prev_idx(0) {
  (void)ptr_handles;
  (void)handle_prev_idx;
}
HandleScope::HandleScope(LEPUSContext *ctx) {}
HandleScope::HandleScope(LEPUSContext *ctx, void *ptr, HandleType type) {}
HandleScope::~HandleScope() {}
void HandleScope::PushHandle(void *ptr, HandleType type) {}
void HandleScope::PushLEPUSAtom(JSAtom atom) {}
void HandleScope::PushLEPUSValueArrayHandle(LEPUSValue *array, int size,
                                            bool need_init) {}
void HandleScope::ResetHandle(void *ptr, HandleType type) {}
void HandleScope::PushLEPUSPropertyDescriptor(LEPUSPropertyDescriptor *desc) {}

void LEPUS_PushHandle(LEPUSContext *ctx, void *ptr, int type) {}
void LEPUS_ResetHandle(LEPUSContext *ctx, void *ptr, int type) {}

void AddCurNode(LEPUSRuntime *rt, void *node, int type) {}
void DeleteCurNode(LEPUSRuntime *rt, void *node, int type) {}
bool CheckValidNode(LEPUSRuntime *rt, void *node, int type) { return true; }

void LEPUS_TrigGC(LEPUSRuntime *rt) {}
void JS_FreeRuntimeForEffect(LEPUSRuntime *rt) {}

char *LEPUS_GetGCTimingInfo(LEPUSContext *ctx, bool is_start) {
  return nullptr;
}

void prim_WriteBarrierNoStore(LEPUSValue value, LEPUSContext *ctx) {}
void prim_HeapObjStoreLEPUSValue(void *fieldAddr, LEPUSValue value) {
  *reinterpret_cast<LEPUSValue *>(fieldAddr) = value;
}

void prim_HeapObjStorePtr(void *dstObj, address_t offset, void *value) {
  void *fieldAddr = (void *)((address_t)dstObj + offset);
  *reinterpret_cast<address_t *>(fieldAddr) = (address_t)value;
}
LEPUSValue js_get_length(LEPUSContext *ctx, LEPUSValueConst obj) {
  return LEPUS_UNDEFINED;
}

void LEPUS_RunAllGC() {}
#endif  // ENABLE_COMPATIBLE_MM

PtrHandles::PtrHandles(LEPUSRuntime *rt) : rt_(rt) { InitialHandles(); }

PtrHandles::~PtrHandles() {
  if (handles) system_free(handles);
}

void PtrHandles::PushHandle(void *ptr, HandleType type) {
  if (UNLIKELY(handle_idx == handle_size - 1)) {
    ResizeHandles();
  }
  handles[handle_idx].ptr = reinterpret_cast<void *>(ptr);
  handles[handle_idx].type = type;
  handle_idx++;
}

void PtrHandles::PushLEPUSValuePtr(LEPUSValue val) {
  int64_t tag = LEPUS_VALUE_GET_TAG(val);
  void *ptr = LEPUS_VALUE_GET_PTR(val);
  switch (tag) {
    case LEPUS_TAG_STRING:
      if ((*(reinterpret_cast<int *>(ptr) - 2) & 0x3F) ==
          ALLOC_TAG_JSConstString)
        break;
      PushHandle(ptr, HANDLE_TYPE_DIR_HEAP_OBJ);
      break;
    case LEPUS_TAG_SEPARABLE_STRING:
    case LEPUS_TAG_FUNCTION_BYTECODE:
    case LEPUS_TAG_LEPUS_REF:
    case LEPUS_TAG_SYMBOL:
    case LEPUS_TAG_BIG_INT:
    case LEPUS_TAG_OBJECT: {
      PushHandle(ptr, HANDLE_TYPE_DIR_HEAP_OBJ);
      break;
    }
    case LEPUS_TAG_Atom: {
      JSAtom atom = LEPUS_VALUE_GET_INT(val);
      if (__JS_AtomIsTaggedInt(atom) || __JS_AtomIsConst(atom)) break;
      PushHandle(ptr, HANDLE_TYPE_DIR_HEAP_OBJ);
      break;
    }
    default:
      // printf("__JS_FreeValue: unknown tag=%d\n", tag);
      break;
  }
}

void PtrHandles::ResetHandle(void *ptr, HandleType type) {
  for (int i = handle_idx - 1; i >= 0; i--) {
    if (handles[i].ptr == ptr && handles[i].type == type) {
      handles[i].ptr = nullptr;
      handles[i].type = HANDLE_TYPE_UNDEFINED;
      return;
    }
  }
}

void PtrHandles::PushLEPUSValueArrayHandle(LEPUSValue *array, int size,
                                           bool need_init) {
  for (int i = 0; i < size; i++) {
    if (need_init) {
      *(array + i) = LEPUS_UNDEFINED;
    }
    PushHandle(array + i, HANDLE_TYPE_LEPUS_VALUE);
  }
}

void PtrHandles::PushLEPUSAtom(JSAtom atom) {
  if (__JS_AtomIsTaggedInt(atom) || __JS_AtomIsConst(atom)) return;
  PushHandle(rt_->atom_array[atom], HANDLE_TYPE_DIR_HEAP_OBJ);
}

void PtrHandles::PushLEPUSPropertyDescriptor(LEPUSPropertyDescriptor *desc) {
  // js_free_desc
  desc->value = LEPUS_UNDEFINED;
  desc->getter = LEPUS_UNDEFINED;
  desc->setter = LEPUS_UNDEFINED;
  PushHandle(&desc->value, HANDLE_TYPE_LEPUS_VALUE);
  PushHandle(&desc->getter, HANDLE_TYPE_LEPUS_VALUE);
  PushHandle(&desc->setter, HANDLE_TYPE_LEPUS_VALUE);
}

// private tools
void PtrHandles::InitialHandles() {
  handles = static_cast<HeapStruct *>(system_malloc(128 * sizeof(HeapStruct)));
  if (!handles) assert(false);
  handle_size = 128;
  handle_idx = 0;
}

void __attribute__((noinline)) PtrHandles::ResizeHandles() {
  int new_size = handle_size * 2;
  HeapStruct *new_handles = static_cast<HeapStruct *>(
      system_realloc(handles, new_size * sizeof(HeapStruct)));
  if (!new_handles) assert(false);
  handles = new_handles;
  handle_size = new_size;
}

CheckTools::CheckTools() : tid_idx(0), tid_size(3) {
  tids = static_cast<int *>(system_malloc(tid_size * sizeof(int)));
  if (!tids) assert(false);
}
CheckTools::~CheckTools() {
  if (tids) system_free(tids);
}

bool CheckTools::PushTid(int tid) {
  if (tid_idx == tid_size - 1) {
    int new_size = tid_size * 2;
    int *new_tids =
        static_cast<int *>(system_realloc(tids, new_size * sizeof(int)));
    if (!new_tids) assert(false);
    tids = new_tids;
    tid_size = new_size;
  }
  tids[tid_idx] = tid;
  tid_idx++;
  return true;
}

bool CheckTools::IsValidTid(int tid) {
  for (int i = 0; i < tid_idx; i++) {
    if (tid == tids[i]) {
      return true;
    }
  }
  return false;
}

void JS_UpdateGCInfo(JSMallocState *s, size_t size) {
  mstate m = &s->allocate_state;
  LEPUSRuntime *rt = static_cast<LEPUSRuntime *>(m->runtime);
  if (m->gc_info_threshold == 0) return;
  m->gc_info_interval_size += size;
  if (m->gc_info_interval_size > m->gc_info_threshold &&
      LEPUS_GetHeapSize(rt) > m->gc_info_threshold) {
    if (rt->gc_enable) {
#ifdef ENABLE_COMPATIBLE_MM
      rt->gc->UpdateGCInfo(0, 0);
#endif
    } else {
      GCObserver *observer = static_cast<GCObserver *>(rt->gc_observer);
      if (observer) {
        std::stringstream gc_info;
        gc_info << "{\n"
                << "  \"gc_info\": [\n"
                << "    {\n"
                << "      \"heapsize_after\": "
                << rt->malloc_state.malloc_size / KB << "    }\n"
                << "  ]\n"
                << "}\n";
        std::string gc_info_str = gc_info.str();
        observer->OnGC(std::move(gc_info_str));
      }
    }
    m->gc_info_interval_size = 0;
  }
}
